JDBC:
Java Database Connectivity。重要接口:
Connection:用于保持和关闭JDBC的连接。Statement:用于将SQL语句发送到数据库。PreparedStatement:Statement的子接口,预编译SQL。ResultSet:结果集,用于接收数据库的返回数据。特点:
为几乎所有主流关系型数据库提供的统一API接口,这些不同的DBMS使用的都是同样的JDBC API,代码都是相同的,避免了程序员在基于不同的DBMS进行编程时可能遇到的困难。JDBC最大的特点在于它不受数据库供应商的限制,而数据库供应商主动提供基于JDBC API的资源包(驱动)。使用:
将数据库供应商提供的驱动导入项目,即可开始数据库开发。packages:
mysql-connector-java-5.x.x-bin.jar(mysql 5)ojdbc14_g.jar(oracle 10g)步骤:
1.导包:
这个不用多说,使用JDBC驱动前务必保证其已经在classpath中。2.如有配置,则加载配置:
比较适合JDBC配置的形式是properties的键值对,mysql JDBC配置的格式为:driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://[主机名或ip地址]/[数据库名]?useUnicode=[是否使用Unicode编码:true/false]&characterEncoding=[字符集] userName=[用户名] password=[密码]
实际例子://DBConfig.properties driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost/MyDatabase?useUnicode=true&characterEncoding=UTF-8 userName=root password=123456
oracle JDBC配置的格式为:driverClassName=Oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@[主机名或ip地址]:[端口,默认为1521]:[数据库名] userName=[用户名] password=[密码]
实际例子://DBConfig.properties driverClassName=Oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@192.168.8.1:1521:yuewei userName=root password=123456
配置了之后,千万不要忘了读取,以备后面的步骤使用。Properties的操作十分简单,就不细说了。//DBUtil.java private static String driverClassName; private static String url; private static String userName; private static String password; public static void loadDBConfig(String filePath){ Properties properties = new Properties(); try { properties.load(new FileInputStream(new File(filePath))); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } driverClassName = (String) properties.get("driverClassName"); url = properties.getProperty("url"); userName = properties.getProperty("userName"); password = properties.getProperty("password"); }
当然也可以不使用配置,直接写在代码里,但这样做将导致更新困难,极不推荐。3.加载驱动
在这一步中,JDBC利用了java的反射机制,利用驱动的类名动态加载驱动。注意,这个驱动加载只需要在启动时加载一次,驱动就会一直装载在JVM中。对于mysql,driverClassName为com.mysql.jdbc.Driver。Class.forName(driverClassName);
对于oracle,driverClassName为oracle.jdbc.OracleDriver。加载驱动时可能产生ClassNotFoundException,需要捕捉。另外,JDK6以后,甚至都不需要使用Class.forName();方法了,只需放在classpath下就会自动加载。4.创建连接对象
利用驱动管理器类的静态方法getConnection即可获得与数据库的连接。Connection connection = DriverManager.getConnection(url, userName, password);
这一步就相当于建立对数据库的连接了。创建连接对象时可能产生SQLException,需要捕捉。5.创建Statement对象
Statement对象由连接对象创建,主要利用建立起的连接执行静态SQL语句,并能返回影响的行数或结果集(ResultSet)对象。Statement statement = connection.createStatement();
创建Statement对象时可能产生SQLException,需要捕捉。6.发起SQL请求
利用Statement对象执行静态SQL语句。发起SQL请求时可能产生SQLException,需要捕捉。
6.1.增删改
对于增删改,将返回一个整型值,代表的是受增删改操作影响的行数,调用者可以利用此。//DBUtil.java int rowAffected = statement.executeUpdate(sql);
6.2.查询
对于查询,将返回一个结果集,调用者需要接收并解析结果集。//DBUtil.java ResultSet resultSet = statement.executeQuery(sql);
7.关闭连接资源
主要是关闭三个资源:Connection、Statement(或PreparedStatement,下文中将介绍)、ResultSet,方法均为close();。特别是Connection,如不及时关闭,面对大量连接时极有可能造成内存溢出。关闭时最好遵循按顺序关闭,先后顺序为ResultSet对象、Statement对象、Connection对象。关闭时可能产生SQLException异常,需要捕捉。还是不够严谨,再加一层判断:try { resultSet.close(); statement.close(); connection.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if(resultSet!=null){ resultSet.close(); if(statement!=null) statement.close(); if(connection!=null) connection.close(); } catch (SQLException e) { e.printStackTrace(); }
回顾:
看完整代码前,先简要回顾一下步骤。1.导包。2.加载配置,包括驱动名、url、用户名、密码,或直接写在代码里。3.加载驱动,JDK6+自动加载。4.创建连接对象,Connection。5.由连接对象创建Statement对象。6.发起SQL连接请求。对于增删改,executeUpdate,返回影响的行数。对于查询,executeQuery,返回结果集ResultSet。7.关闭各种对象,ResultSet,Statement,Connection。实例:
下面来一个完整的实例吧。/** * JDBC工具类 * @author hsdsmljj * */ public class DBUtil { private static String driverClassName; private static String url; private static String userName; private static String password; public static void loadDBConfig(String filePath){ //加载配置properties Properties properties = new Properties(); try { properties.load(new FileInputStream(new File(filePath))); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //读取配置 driverClassName = (String) properties.get("driverClassName"); url = properties.getProperty("url"); userName = properties.getProperty("userName"); password = properties.getProperty("password"); //加载JDBC驱动 try { Class.forName(driverClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 建立数据库连接 * */ public static Connection getConnection(){ Connection connection = null; //利用DriverManager(驱动管理器)的静态方法建立连接 try { connection = DriverManager.getConnection(url, userName, password); } catch (SQLException e) { e.printStackTrace(); } return connection; } /** * 执行增删改 * @param sql * @return 影响的行数 */ public static int update(String sql){ //建立连接 Connection connection = getConnection(); Statement statement = null; int rowAffected = 0; try{ //由连接对象创建Statement对象 statement = connection.createStatement(); //执行增删改,返回影响的行数 rowAffected = statement.executeUpdate(sql); //自动关闭Statement及Connection对象 close(statement, connection); }catch (SQLException e) { e.printStackTrace(); } return rowAffected; } /** * 执行查询 * @param sql * @param statement * @param connection * @return 结果集 */ public static ResultSet query(String sql, Statement statement, Connection connection){ <span style="white-space:pre"> </span>//预先准备好connection及statement ResultSet resultSet = null; try{ //执行查询,返回结果集 resultSet = statement.executeQuery(sql); //不能在此处关闭Statement及Connection对象,需要等result set读取使用完毕再依次关闭 //close(statement, connection); }catch (SQLException e) { e.printStackTrace(); } return resultSet; } /** * 关闭ResultSet对象 * @param resultSet */ public static void closeResultSet(ResultSet resultSet){ try{ if(resultSet!=null) resultSet.close(); }catch (SQLException e) { e.printStackTrace(); } } /** * 关闭Statement及Connection对象 * @param resultSet */ public static void close(Statement statement, Connection connection){ try{ if(statement!=null) statement.close(); if(connection!=null) connection.close(); }catch (SQLException e) { e.printStackTrace(); } } }
/** * JDBC工具类DBUtil的测试类 * @author hsdsmljj */ public static void main(String[] args) { //载入配置 DBUtil.loadDBConfig("conf/DBConfig.properties"); String querySql = "SELECT * FROM sometable"; String insertSql = "INSERT INTO sometable VALUES('somenewvalue')"; String updateSql = "UPDATE sometable SET value='someothervalue'"; String deleteSql = "DELETE FROM sometable WHERE value='someothervalue'"; try { Connection connection = DBUtil.getConnection(); Statement statement = connection.createStatement(); //查询,返回结果集 ResultSet resultSet = DBUtil.query(querySql, statement, connection); //解析结果集,使用ResultSet类的next()方法迭代移动游标 while(resultSet.next()){ //使用ResultSet类的getInt或getString等方法获取每一行的各个属性值 //也可以用列的位置(int)来获取,位置索引从1开始 String value = resultSet.getString("value"); //对数据进行处理,组装成对象等 ... } //关闭结果集等对象 DBUtil.closeResultSet(resultSet); DBUtil.close(statement, connection); } catch (SQLException e) { e.printStackTrace(); } //执行增删改,自动关闭各种对象 int affectedRows1 = DBUtil.update(insertSql); int affectedRows2 = DBUtil.update(updateSql); int affectedRows3 = DBUtil.update(deleteSql); }
预编译:
java中的PreparedStatement 接口继承了Statement,并与之在两方面有所不同:有人主张,在JDBC应用中,如果你已经是稍有水平开发者,你就应该始终以PreparedStatement代替Statement。也就是说,在任何时候都不要使用Statement。
---百度百科《PreparedStatement》词条
既然度娘都这么说了,那我等自然就要用“稍有水平开发者”的水准来要求自己,不是嘛?
数据库引擎执行SQL语句的大致过程是:
1.Sql分析,语法检测。
2.解析编译。
3.绑定数据。
4.安排计划。
5.执行(commit)。
6.返回数据。
其中1-2占的时间较大,预编译能在一次性执行大量SQL时让引擎只需检测、编译一次,故节省时间。
在java中,要使用预编译,需要修改上述步骤的第五到第六步。
创建PreparedStatement预编译执行器对象:
在这一步,数据先不填,比如insert语句的value,或update语句的set 字段的值,用问号代替。String sql = "insert into sometable values(?,?)"; //PreparedStatement中的字符串参数不能加引号! PreparedStatement preStat = conn.prepareStatement(sql);
绑定数据并执行:
先来十万条。
int affectedRows = 0; for(int i=1;i<100000;i++){ preStat.setInt(1,i); //参数下标从1开始 preStat.setString(2,"value"+i); affectedRows += preStat.executeUpdate(); }
或者加批处理,风味更佳(可选):
for(int i=1;i<100000;i++){ preStat.setInt(1,i); preStat.setString(2,"value"+i); preStat.addBatch(); } int affectedRows = preStat.executeBatch();
与Statement比较:
PreparedStatement适用于发起大爆发量的SQL请求(这里说的请求是增删改,谁没事不停地查询十万次),在这种情形下其效率比Statement高出很多。PreparedStatement还有一个好处就是用绑定数据对象的方式来完成sql语句的构造,而不是像Statement那样用字符串拼接,出错率会比Statement低。
另外,PreparedStatement利用占位符“?”来传参,也在一定程度上防御了sql注入。
当然,Statement仍有其用武之地,不然早就被deprecated了。当面对一条或者若干条SQL语句时,Statement的代码量更小,更加简单易读。
另外,写法上,Statement将sql语句在请求阶段传入,而PreparedStatement则在创建其对象时传入,如下:
//Statement的语法 Statement stat = conn.createStatement(); //空参 stat.executeUpdate(sql); //传入完整SQL语句参数
//PreparedStatement语法 PreparedStatement preStat = conn.prepareStatement(sql); //传入不完整的、待绑定数据对象的SQL语句参数 preStat.executeUpdate(); //空参
DBCP:
Database Connection Pool
packages:
common-dbcp.jar
common-pool.jar
common-collections.jar
来由:
在JDBC的请求过程中,建立连接与释放连接所耗时间所占整个请求过程的时间比重最大。
再者,每次发起SQL请求都需要建立连接,最后再关闭连接,太过麻烦。
为了应对这种频繁建立与释放连接的需求,预先建立好一系列连接,放在连接缓冲池中,需要时从池中取用,这就是连接池的来由。
原理:
动态代理。
过程:
1.App启动。
2.读取配置。
3.根据配置发起多个连接,放入连接池中待取用。
4.取连接,发起SQL请求,SQL请求完毕后,连接放回连接池中。
连接池管理:
1.接到取连接请求。
若连接池中连接数足够,取出并返回连接。
若连接池中连接数不够,创建新的连接并返回。
2.连接空闲超时时间到。
若空闲连接数小于允许的空闲连接数,不操作。
若空闲连接数超过允许的空闲连接数,关闭超额的超时空闲连接。
使用:
下面是一个使用了连接池的数据库JDBC工具类。//DBCPUtil.java import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; public class DBCPUtil{ private static BasicDataSource bds; private static String dbDriver; private static String url; private static String userName; private static String password; private static int maxActive; private static int maxIdel; private static int maxWait; /** * 此方法载入时静态调用 * */ protected static void createBasicDataSource() { try { ...// 读取配置文件中的配置 bds = new BasicDataSource(); // 建立数据源 bds.setDriverClassName(dbDriver); // JDBC驱动 bds.setUrl(url); // url bds.setUsername(userName); // userName bds.setPassword(password); // password bds.setMaxActive(maxActive); // 最大连接数 bds.setMaxIdle(maxIdle); // 最大空闲数 bds.setMaxWait(maxWait); // 最大等待时间 } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接,不需手动调用 * */ private static Connection getConn() { // 不需手动关闭,请求完毕后,连接回到连接池中,并在超时时关闭超额的连接 Connection conn = bds.getConnection(); return conn; } /** * 增删改 * */ public static int update(String sql) { int rowAffected = 0; try{ Statement stat = getConn().createStatement(); rowAffected = stat.executeUpdate(sql); if(stat!=null) stat.close(); } catch(SQLException e){ e.printStackTrace(); } return rowAffected; } /** * 查 * */ public static ResultSet query(String sql, Statement stat){ ResultSet rs = null; try{ //预先准备好statement对象 rs = stat.executeQuery(sql); } catch(SQLException e){ e.printStackTrace(); } return rs; } /** * 手动关闭result set * @param rs */ public void closeResultSet(ResultSet rs){ try { if(rs!=null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } } /** * 手动关闭statement * @param stat */ public void closeStatement(Statement stat){ try { if(stat!=null) stat.close(); } catch (SQLException e) { e.printStackTrace(); } } }
//Test.java import java.sql.ResultSet; import java.sql.Statement; import java.sql.SQLException; public class Test { public static void main(String[] args){ String querySql = "SELECT * FROM sometable"; String insertSql = "INSERT INTO sometable VALUES('somenewvalue')"; String updateSql = "UPDATE sometable SET value='someothervalue'"; String deleteSql = "DELETE FROM sometable WHERE value='someothervalue'"; Statement stat = DBCPUtil.getConn().createStatement(); ResultSet rs = DBCPUtil.query(querySql, stat); try { while(rs.next()){ String value = rs.getString("value"); //对数据进行处理,组装成对象等 } } catch (SQLException e) { e.printStackTrace(); } DBCPUtil.closeResultSet(rs); DBCPUtil.closeStatement(stat); DBCPUtil.update(insertSql); DBCPUtil.update(updateSql); DBCPUtil.update(deleteSql); } }