java JDBC介绍

1.JDBC

1.1.数据库驱动

1.1.1.数据库驱动的概念

      数据库厂商提供的用来操作数据库的jar包就叫做数据库的驱动。
      把某个java项目打包成jar文件,提供给其他人使用。。
使用的时候,解压jar文件,生成一个文件夹,里面存放的就是编译好的每个包下面的类文件。。

1.2.JDBC

1.2.1.JDBC的概念

      JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。
      由于不同的数据库厂商提供的数据库驱动各不相同,在使用不同数据库时需要学习对应数据库驱动的api,对于开发人员来说学习成本十分的高。
      于是sun提供了JDBC的规范,也就是jar包,本质上一大堆的接口,要求不同的数据库厂商提供的驱动都实现这套接口,这样以来开发人员只需要学会JDBC这套接口,所有的数据库驱动作为这套接口的实现,就都会使用了。

1.2.2.JDBC包

      JDBC主要是由 java.sql javax.sql包组成的,并且这两个包已经被集成到J2SE的规范中了,这意味着,只要一个普通的java程序就可以使用JDBC。
      要注意的是,在开发数据库程序时,除了如上的两个包,还需要手动的导入具体的数据库驱动。
      注意: jdbc只是一套接口,具体操作数据库的代码都在接口的实现类中,也就是数据库驱动中,开发时还是要导入具体的数据库驱动.
java JDBC介绍_第1张图片
java JDBC介绍_第2张图片

1.3.六个步骤实现JDBC

      在导入Connection、Statement、ResultSet包的时候,导的都是接口而不是实现类,是为了程序更加通用,不管是mysql还是Oracle数据库或者其他数据库都可以接收住。

Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
	//1.注册数据库驱动      告诉程序这是个驱动器
	DriverManager.registerDriver(new Driver());//一般不推荐使用这种方式
    //2.获取数据库的连接
	conn=DriverManager.getConnection("jdbc:mysql:///mydb1",user="root",password="root");
	//3.获取传输器对象
	stat = conn.createStatement();
	//4.传输sql语句到数据库中执行,获取结果集对象
	rs = stat.executeQuery("select * from user");
	//5.遍历结果集获取需要的数据
	while(rs.next()){
		String name = rs.getString("name");
		Date date = rs.getDate("birthday");
       	System.out.println(name+":"+password);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}finally{
		//6.关闭资源。后获取的先关闭(注意关闭顺序)
		if(rs != null){        //不判段空,如果赋值执行不到会出空指针异常
			try {rs.close();} catch(SQLException e)   
            {e.printStackTrace();} finally{rs = null;}
		}
		if(stat != null){
			try {stat.close();} catch (SQLException e)
             {e.printStackTrace();} finally{stat = null;}
		}
		if(conn != null){
			try {conn.close();} catch (SQLException e){
            e.printStackTrace();}finally{conn = null;}
		}
	}

1.4.JDBC API详解

1.4.1.注册数据库驱动

      使用DriverManager.registerDriver(new Driver())方式注册数据库有两个缺点,首先,通过观察mysql的中Driver接口的实现类发现在静态代码块中注册驱动的逻辑,所以这种方式会造成驱动被注册两次。另外,这种方式导致了程序和具体的数据库驱动绑死在了一起,程序的灵活性比较低。
      所以推荐使用:Class.forName(“com.mysql.jdbc.Driver”);的方式注册数据库驱动。 使用这种方式程只是和数据库驱动的全限定名绑死在一起,后期可以将全限定名提取到配置文件中。

获取数据库连接
Connection conn = DriverManager.getConnection(url,name,psw);

1.4.2.数据库URL

      URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:

  jdbc:mysql://localhost:3306/test ?参数名=参数值

      常用数据库URL地址的写法:

/*Oracle写法:*/
jdbc:oracle:thin:@localhost:1521:sid

/*SqlServer写法:*/
jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sid

/*MySql:*/
jdbc:mysql://localhost:3306/sid  

/*如果是是访问本机的数据库,localhost可以省略不写,如果在配置数据库的时候,端口号默认的是3306,那么3306也可以省略不写。
Mysql的url地址的简写形式: */
jdbc:mysql:///sid

1.4.3.Connection

      Jdbc程序中的Connection,它用于代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:

createStatement() //:创建向数据库发送sql的statement对象。
prepareStatement(sql): //创建向数据库发送预编译sql的PreparedSatement对象。
prepareCall(sql) //:创建执行存储过程的callableStatement对象。 
setAutoCommit(boolean autoCommit) //:设置事务是否自动提交。 
commit() //:在此连接接上提交事务。
rollback() //:在此连接上回滚事务。 (当事务提交失败的时候可自动回滚事务)

1.4.4.Statement

      Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:

executeQuery(String sql)// :用于向数据库发送查询语句。
executeUpdate(String sql)//:用于向数据库发送insert、update或delete语句
返回一个int值,代表影响的行数。。
execute(String sql)//:用于向数据库发送任意sql语句   
//返回一个boolean值,如果sql语句是查询,则会返回true,如果sql语句是更新(增、删、改),则会返回false.
addBatch(String sql)//:把多条sql语句放到一个批处理中。
executeBatch()//:向数据库发送一批sql语句执行。 

1.4.5.ResultSet

      Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
Result的next()方法。不仅用于判断是否有下一行语句,还有把光标下移的功能
ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:

//获取任意类型的数据
getObject(int index)   (传入的参数是列的下标)
getObject(string columnName)  (传入的参数是列的名称)

获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
getInt(columnIndex)
getInt(columnLabel)
getDouble(columnIndex)
getDouble(columnLabel)
//操作游标的方法,例如:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。(第一行的前面)
afterLast() :移动到resultSet的最后面。 (最后一行的后面)

1.4.6.释放资源

      Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
      释放资源的时候注意顺序:要按先 ResultSet 结果集,后 Statement,最后 Connection 的顺序关闭资源,因为 Statement 和 ResultSet 是需要连接时才可以使用的,所以在使用结束之后有可能其它的 Statement 还需要连接,所以不能现关闭 Connection。
      特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
      为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。

import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;

/**
 * JDBC工具类
 * @author Administrator
 *
 */
public class JDBCUtils {
	private static Properties prop = new Properties();
	//构造方法私有化,防止其他类在外面new对象
	private JDBCUtils(){}

    //在程序启动时读取配置文件
	static{
		//读取配置文件
		 try {
			 //获得一个类加载器
			 ClassLoader classLoader = JDBCUtils.class.getClassLoader();
			 //类加载器直接从bin目录下去找
			 String path = classLoader.getResource
                                ("config.properties").getPath();
			prop.load(new FileInputStream(path));
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);  //转化成运行时异常抛出
		}
	}
	
	/*
	 * 获取连接对象
	 */
	public static Connection getConnection(){
		Connection conn = null;
		try {
			String driverClass = prop.getProperty("driverClass");
			String jdbcurl = prop.getProperty("jdbcurl");
			String user = prop.getProperty("user");
			String password = prop.getProperty("password");
			Class.forName(driverClass);
			conn = DriverManager.getConnection(jdbcurl,user,password);
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);  //转化成运行时异常抛出
		}
		return conn;
	}
	
	/*
	 * 释放资源
	 */
	public static void close(Connection conn,Statement stat,ResultSet rs){
		if(rs != null){
		   try {rs.close();} 
           catch (SQLException e) {e.printStackTrace();}finally{rs = null;}
		}
		if(stat != null){
		   try {stat.close();} 
           catch(SQLException e){e.printStackTrace();}finally{stat= null;}
		}
		if(conn != null){
			try {conn.close();} 
           catch(SQLException e){e.printStackTrace();}finally{conn=null;}
		}
	}		
}

1.5.SQL注入攻击

1.5.1 Sql注入攻击演示

select * from user where username='张三'#'' and password=''
select * from user where username='张三' or '2=2' and password=''

发现无需输入密码,直接登录,发现无需密码登录了进去。这就是发生了SQL注入问题。

1.5.2 Sql注入攻击的原理

      
由于jdbc程序在执行的过程中sql语句在拼装时使用了由页面传入参数,如果用户,恶意传入一些sql中的特殊关键字或者特殊符号,会导致sql语句意义发生变化,从而造成一些意外的操作,这种攻击方式就叫做sql注入。
      如何防止SQL注入攻击呢?
      这时候就需要用到PreparedStatement对象。

1.6 PreparedStatement

1.6.1.PreparedStatement对象

      PreparedStatement是Statement的子接口,不同的是,PreparedStatement使用预编译机制,在创建PreparedStatement对象时就需要将sql语句传入,传入的过程中参数要用?替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatement的setXXX将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。
      PreparedStatement使用了预编译机制,sql语句在执行的过程中效率比Statement要高。
      PreparedStatement使用了 “?”通配符省去了字符串的拼接,使代码更加优雅。

PreparedStatement的优点:(如果SQL语句有参数)
(1)可以防止sql注入攻击
通过PreparedStatement对象发送SQL,是先把SQL语句的骨架发送给数据库编译并确定下来,后面发送的只能是参数的值,不能影响SQL语句的骨架,即使参数中包含SQL关键字或者特殊符号,也只会当成普通的文本来处理。
(2)通过方法来设置参数,省去了拼接SQL语句的麻烦
(3)可以提高程序的效率:
通过PreparedStatement对象发送的SQL语句(骨架)到数据库编译后会被数据缓存下来,如果下次执行的SQL与缓存中的相匹配,就不用再编译而是直接使用缓存中的语句,可以减少SQL语句编译的次数,提高程序执行的效率。

Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。

如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。





Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。

如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。

2.批处理

2.1.批处理业务场景

      概述:
      假设现有一大堆的SQL语句要到数据库中去执行,如果一条一条发送,有多少条就要发送多少次,效率低下。
      可以通过批处理提高发送SQL语句的效率:可以将这一大堆的SQL语句添加到一个批中,一次性将批发送给数据库, 数据库收到后打开批, 依次执行其中sql语句, 这样可以减少sql语句发送的次数, 从而提高程序执行的效率!

2.1.1.批处理业务场景

      当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。

2.1.2.Statement方式实现批处理

Statement.addBatch(sql) 

执行批处理SQL语句

executeBatch()方法:执行批处理命令
clearBatch()方法:清除批处理命令。   将该批中的语句清除,再次放入新的执行的语句
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
	conn = JdbcUtil.getConnection();
	String sql1 = "insert into per(name,password)values('kk','123')";
	String sql2 = "update user set password='123' where id=3";
	st = conn.createStatement();
	st.addBatch(sql1);  //把SQL语句加入到批命令中
	st.addBatch(sql2);  //把SQL语句加入到批命令中
	st.executeBatch();   //执行批处理命令
} finally{
    JdbcUtil.free(conn, st, rs);
}

采用Statement.addBatch(sql)方式实现批处理:
优点:
      可以在一次批处理中发送多条结构不同的SQL语句。
缺点:
      不能防止SQL注入
      SQL语句没有预编译,效率较低。
      当向数据库发送多条结构相同的语句时,SQL语句的骨架每次都要编写。

2.1.3.PreparedStatement方式实现批处理

PreparedStatement.addBatch()
conn = JdbcUtil.getConnection();
String sql = "insert into person(name,password)values(?,?)";
ps = conn.prepareStatement(sql);
conn.setAutoCommit(false);   //关闭自动提交事务
for(int i=0;i<50;i++){
    ps.setString(1, "aaa" + i);
	ps.setString(2, "123" + i);
	ps.addBatch();  //添加到批处理,但是此时没有处理   如果此时没有
    }                 //加到批处里面,执行之后数据库不会有任何改动
}              //因为此时相当于在执行一个空的批,所以没有任何sql语句被执行
ps.executeBatch();
conn.commit();   //最后手动提交一次批处理的事务

采用PreparedStatement.addBatch()实现批处理:
优点:
      可以防止sql注入。
      发送的是预编译后的SQL语句,执行效率高。
      当发送多条结构相同的sql时,SQL语句的骨架可以只发送一次。
缺点:
      不能在一次批处理中添加结构不同的sql语句。
      因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。

3.连接池

3.1.连接池概述

      用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。(使用连接并没有消耗多少资源,浪费多长时间。)假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
java JDBC介绍_第3张图片
频繁的开关连接相当的耗费资源,所以我们可以设置一个连接池,在程序启动时就初始化一批连接,在程序中共享,需要连接时从池中获取,用完再还回池中,通过池共享连接,减少开关连接的次数,提高程序的效率。
java JDBC介绍_第4张图片
Sun公司为连接池提供 javax.sql.DataSource接口,要求连接池去实现,所以连接池也叫数据源。
我们可以自己实现这个接口来实现一个连接池。

import java.io.PrintWriter;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import javax.sql.DataSource;

public class MyPool implements DataSource {
	private static List<Connection> pool=new LinkedList<Connection>();
	static{
		try{
			Class.forName("com.mysql.jdbc.Driver");
			for(int i=0;i<5;i++){
				Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
				pool.add(conn);
			}
		}catch (Exception e) {e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
   //获取一个连接
	public Connection getConnection() throws SQLException {
		if(pool.isEmpt()){
			for(int i=0;i<3;i++){
				Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
				pool.add(conn);
			}
		}
		return pool.remove(0);
	}

//将连接返回给连接池	
	private void retConn(Connection conn){
		try {
			if(conn!=null && !conn.isClosed()){
				pool.add(conn);
			 }
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	public Connection getConnection(String username, String password)
			throws SQLException {return null;}
	public PrintWriter getLogWriter() throws SQLException {}
	public int getLoginTimeout() throws SQLException {return 0;}
	public void setLogWriter(PrintWriter out) throws SQLException {}
	public void setLoginTimeout(int seconds) throws SQLException {}
	public boolean isWrapperFor(Class<?> iface) throws SQLException 
      {return false;}
	public<T> T unwrap(Class<T> iface) throws SQLException 
      {return null;}
}

当从连接池中获取连接,需要在使用完后不能关闭连接,而是要调用retConn方法将连接还回池中。

你可能感兴趣的:(java,java-ee,数据库,mysql)