JDBC开发遵循以下步骤: 1. 导入JDBC的jar包,并添加至buildPath路径; 2. 加载驱动类(Driver类); 3. 获取数据库连接(Connection类); 4. 获取执行SQL语句的对象(Statement或PreparedStatement类); 5. 获取SQL语句执行的结果集对象(ResultSet对象); 6. 释放资源(Connection,Statement或PreparedStatement,ResultSet资源释放).
JDBC开发过程中数据库连接的处理及获取结果集对象处理较为常用和重要,则需要针对原始的数据库连接和结果集对象的处理优化.即将开发步骤的第3步和第5步进行优化.
针对数据库连接的优化是模仿线程池技术采用数据库连接池技术,同时也将加载驱动类进行了同步的封装.
针对数据库查询结果集的处理优化是采用对结果集进行封装的技术(DbUtils工具类),同时对执行SQL语句的对象进行了封装处理.
数据库连接对象的创建和销毁会消耗资源,为避免频繁的重复此操作,可以模仿线程池的原理设置数据库连接池提高效率. 数据库连接池原理是预先创建一批数据库连接对象,然后需要使用时直接从连接池进行获取,不再进行创建,使用完后不关闭,而是将使用完的连接对象归还到连接池中. 实现数据库连接池需要注意的问题:数据库连接对象的存储和回收.
JDK提供数据库连接池的接口DataSource.
自定义类实现数据库连接池DataSource接口,在该类中定义集合用于存储连接对象,重写DataSource的getConnection()方法,在该方法中返回Connection对象,使用自定义方法将使用完的Connection对象再返回到数据库连接池中.代码实现如下:
/** * 数据库连接池对象 */ public class My_DataSource implements DataSource { // 定义存储连接的集合 private List<Connection> connList = new ArrayList<Connection>(); // 通过构造方法初始化连接集合,数量为3个 public My_DataSource(){ for (int i = 0; i < 3; i++) { // 通过JDBCUtils工具类获取Connection连接对象 Connection conn = JDBCUtils.getConnection(); // 将Connection对象添加到集合中 connList.add(conn); } } // 重写DataSource的getConnection方法,返回的是此连接池中预先定义好的Connection对象 @Override public Connection getConnection() throws SQLException { // 返回数据库连接 if(connList.size() <= 0) { // 当连接集合中连接数量少于0时,继续创建一批连接对象 for (int i = 0; i < 3; i++) { Connection conn = JDBCUtils.getConnection(); connList.add(conn); } } // 每次返回连接池中第一个,移除集合中第一个对象 Connection conn = connList.remove(0); return conn; } // 自定义归还数据库连接对象的方法,将使用完的连接对象再添加回集合中 public void returnConnection(Connection conn) { connList.add(conn); } ...DataSource的其他方法省略 }上述实现数据库连接池的弊端:
在上述的实现思想中将数据库连接对象返回到连接池中时,使用的是自定义方法归还,这种实现方式不符合面向接口编程的原则,并且使用自定义方法归还连接对象,增加了该连接池类的使用成本.
使用DataSource的接口的获取的是Connection对象,可以将Connection对象的close()方法原有的关闭连接的功能改变为归还该连接对象到数据库连接池,该思想良好的遵循面向接口编程,并且没有增加新的方法.
增强或改变一个方法的功能可用的实现方式:
使用继承的方式:
继承父类的方法,可以进行父类方法的重写来增强父类方法的功能;
要求:子类必须能够控制父类的构造方法,才能使用继承(必须知道明确的父类及父类能被继承并且要增强的方法可以被重写).
装饰者设计模式:
将已有的类进行包装,将其中某些方法进行增强;
要求:包装对象和被包装的对象有实现的相同接口;包装对象中有被包装对象的引用.
缺点:如果接口的方法比较多,增强其中的某个方法.其他的功能的方法需要使用被包装对象去调用原有的方法.
动态代理的方式:
要求:需要增强的方法所属的类有实现的接口.
由于使用的mysql数据库提供的JDBC的jar包中实现的Connection接口类无法找到,无法使用继承的方式进行close方法功能的改造,则可以使用装饰者模式和动态代理的方式实现功能的改造,此处使用装饰者模式进行原始的Connection接口的包装;使用自定义类包装Connection接口中的close()方法,在获取数据库连接池中的连接对象时,将原始的Connection对象进行包装再返给调用者,则在调用者归还连接对象调用close方法时会调用被包装过的close方法,将连接对象归还到连接池中, 代码如下:
/** * 原始的connection类的包装类 * 重写里面的close方法,将close功能改为将连接不进行销毁,将连接归还到连接池 */ public class My_Connection implements Connection { // 获得被包装对象的引用 private Connection conn; // 获取连接池中的存储连接对象的集合对象,方便将使用完毕的Connection对象再添加到集合中 private List<Connection> connList; // 通过构造方法获取被包装类的引用和连接对象存储的集合对象 public My_Connection(Connection conn,List<Connection> connList) { this.conn = conn; this.connList = connList; } // 重写Connection接口中的close方法 @Override public void close() throws SQLException { // 改造Connection的方法,将连接归还到连接池 connList.add(conn); } ...Connection接口中其他方法通过conn对象调用原有的方法 } /** * 数据库连接池对象 */ public class My_DataSource implements DataSource { // 定义存储连接的集合 private List<Connection> connList = new ArrayList<Connection>(); // 通过构造方法初始化连接集合,数量为3个 public My_DataSource(){ for (int i = 0; i < 3; i++) { Connection conn = JDBCUtils.getConnection(); connList.add(conn); } } // 重写DataSource接口中的getConnection方法 @Override public Connection getConnection() throws SQLException { // 返回数据库连接 if(connList.size() <= 0) { // 当连接集合中连接数量少于0时,重新生成 for (int i = 0; i < 3; i++) { Connection conn = JDBCUtils.getConnection(); connList.add(conn); } } // 每次返回连接池中第一个,并冲集合中移除 Connection conn = connList.remove(0); // 返回包装的Connection类,使用My_Connection类将conn进行包装 My_Connection mconn = new My_Connection(conn, connList); // 返回被包装后的Connection对象 // 返回是Connection对象conn,实际上指向的是My_Connection对象mconn,conn使用的方法都是通过mconn进行中转的,当使用完毕该连接对象调用close()方法时,通过mconn对象中转该方法时没有调用conn的close方法二是执行的自定义的逻辑代码,将该连接对象返回到集合中 return mconn; } ...DataSource的其他方法省略 }dbcp是Java的数据库连接池,也是tomcat服务器软件使用的连接池组件.本身会自动创建一定数量的Connection对象供使用,并且dbcp已经对象Connection接口的close()方法进行了包装,不是原始的关闭资源,而是将连接对象归还到连接池中. 使用dbcp连接池需要两个jar的支持:commons-dbcp-版本号.jar和commons-pool-版本号.jar
dbcp连接池核心类:BasicDataSource类(手动配置)和BasicDataSourceFactory类(加载配置文件)
dbcp配置信息设置有两种方式:手动在程序中配置和读取配置文件.
手动配置dbcp信息:
/** * 手动设置参数 */ public void demo1(){ // 通过BasicDataSource对象创建数据库连接池对象 BasicDataSource bds = new BasicDataSource(); // 加载驱动 bds.setDriverClassName("com.mysql.jdbc.Driver"); // 设置数据库的url bds.setUrl("jdbc:mysql://localhost:3306/jdbc"); // 数据库的用户名 bds.setUsername("root"); // 数据库的密码 bds.setPassword("root"); // 定义连接对象,执行SQL语句对象,结果集对象 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 通过连接池获取连接 conn = bds.getConnection(); // 定义SQL语句 String sql = "select * from user"; // 预编译SQL语句 ps = conn.prepareStatement(sql); // 获取执行的SQL结果集 rs = ps.executeQuery(); // 遍历结果集并输出 while(rs.next()) { System.out.println(rs.getInt(1)+"---"+rs.getString(2)+"---"+rs.getString(3)); } } catch(Exception e) { e.printStackTrace(); } finally { // 使用自定义工具类关闭资源,实际上conn的关闭是将连接归还到连接池 JDBCUtils.release(conn, ps, rs); } }加载配置文件:
# dbcpconfig.properties配置文件的简单配置信息,还可以根据需要添加其他的配置信息 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbc username=root password=root /** * 加载配置文件方式 */ public void demo1(){ // 创建配置文件加载的properties对象 Properties prop = new Properties(); // 定义连接对象,执行SQL语句对象,结果集对象 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 获取My_DBCP_Demo类的.class文件路径下的指定名称的文件的字节输入流对象 InputStream is = My_DBCP_Demo.class.getClassLoader(). getResourceAsStream("dbcpconfig.properties"); // 加载指定的输入流对象资源 prop.load(is); // 也可以使用创建FileInputStream读取配置文件 // prop.load(new FileInputStream("src/jdbc.properties")); // 通过BasicDataSourceFactory工程类创建加载指定配置文件的连接池对象 DataSource ds = BasicDataSourceFactory.createDataSource(prop); // 通过连接池获取连接 conn = ds.getConnection(); // 定义SQL语句 String sql = "select * from user"; // 预编译SQL语句 ps = conn.prepareStatement(sql); // 获取执行的SQL结果集 rs = ps.executeQuery(); // 遍历结果集并输出 while(rs.next()) { System.out.println(rs.getInt(1)+"---"+rs.getString(2)+"---"+rs.getString(3)); } } catch(Exception e) { e.printStackTrace(); } finally { // 使用自定义工具类关闭资源,实际上conn的关闭是将连接归还到连接池 JDBCUtils.release(conn, ps, rs); } }加载配置文件的方式:
使用类加载器的方式获得配置文件的字节输入流:
要求:该配置文件必须要与使用的类的加载器的.class文件(类的加载路径)在统一目录下,如
// 配置文件必须和My_DBCP_Demo类的.class文件(类的加载路径)在统一目录下 My_DBCP_Demo.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");注意事项:类的加载路径根据项目工程类型有所不同
myeclipse创建的Java工程:配置文件一般是在bin目录下;
myeclipse创建的web工程:配置文件是在WebRoot\WEB-INF\classes目录下;
在项目工程的src目录下的资源文件都会在编译时创建一个副本到本工程的类加载路径下.
一般将配置文件放在src目录下,在编译时会自动在类加载路径下创建一个资源的副本.
使用FileInputStream类加载配置文件:
使用该方式加载配置文件对配置文件的位置没有特殊要求,但在访问时要注意配置文件的路径问题.
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展,目前使用它的开源项目有Hibernate,Spring等.本身会自动创建一定数量的Connection对象供使用,并且dbcp已经对象Connection接口的close()方法进行了包装,不是原始的关闭资源,而是将连接对象归还到连接池中. 使用c3p0连接池需要jar支持:c3p0-0.9.1.2.jar c3p0连接池的核心类:ComboPooledDataSource类.
c3p0连接池的配置信息设置方式:手动设置和加载配置文件.
手动设置配置信息:
/** * 手动设置配置c3p0信息 */ public void demo1() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; ComboPooledDataSource cps = new ComboPooledDataSource(); try { cps.setDriverClass("com.mysql.jdbc.Driver"); cps.setJdbcUrl("jdbc:mysql:///jdbc"); cps.setUser("root"); cps.setPassword("root"); // 获取的Connection对象是已经经过包装的Connection对象,其close方法是归还连接对象 conn = cps.getConnection(); ps = conn.prepareStatement("select * from user"); rs = ps.executeQuery(); while(rs.next()) { System.out.println(rs.getInt(1)+"---"+rs.getString(2)+"---"+rs.getString(3)); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.release(conn, ps, rs); } }加载配置文件:
c3p0连接池是自动加载配置文件,其加载配置文件支持properties类型和xml格式的文件.xml文件的文件名固定为c3p0-config.xml.并且需要将配置文件放在类加载路径下,Java项目工程是在bin目录下,web项目工程是在web-inf/classes目录下;可以将配置文件直接放在src目录下,编译时会自动将配置文件创建一个副本到类的加载路径下.
当c3p0-config.xml文件中可以有多个数据库的配置文件,默认的是\标签内的配置信息,也可以自定义名称的配置信息,在程序中创建ComboPooledDataSource对象将指定名称的配置的name属性值作为其参数即可使用指定名称的配置信息创建连接池,当程序中找不到指定名称的配置信息时会去加载默认的配置信息
<!--c3p0-config.xml文件--> <?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--默认的配置信息--> <default-config> <!--还可以添加其他支持的字段的配置信息--> <!--数据库驱动--> <property name="driverClass">com.mysql.jdbc.Driver</property> <!--数据库连接url--> <property name="jdbcUrl">jdbc:mysql:///jdbc</property> <!--数据库用户名--> <property name="user">root</property> <!--数据库用户名--> <property name="password">root</property> </default-config> <!--指定名称的的配置信息--> <named-config name="MyC3P0"> <!--还可以添加其他支持的字段的配置信息--> <!--数据库驱动--> <property name="driverClass">com.mysql.jdbc.Driver</property> <!--数据库连接url--> <property name="jdbcUrl">jdbc:mysql:///jdbc</property> <!--数据库用户名--> <property name="user">root</property> <!--数据库用户名--> <property name="password">root</property> </named-config> </c3p0-config> /** * 加载配置c3p0配置文件 */ public void demo2() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; // 使用默认的配置信息 ComboPooledDataSource cps = new ComboPooledDataSource(); // 使用指定名称的的配置信息,当找不到MyC3P0名称的配置信息时,就会去加载默认的配置信息 // ComboPooledDataSource cps = new ComboPooledDataSource("MyC3P0"); try { conn = cps.getConnection(); ps = conn.prepareStatement("select * from user"); rs = ps.executeQuery(); while(rs.next()) { System.out.println(rs.getInt(1)+"---"+rs.getString(2)+"---"+rs.getString(3)); } } catch (Exception e) { e.printStackTrace(); } finally { // c3p0连接池中将Connection的close方法进行了处理,调用close方法就是将连接归还到连接池 JDBCUtils.release(conn, ps, rs); } }DbUtils类是对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能. DbUtils工具类依赖的jar包:commons-dbutils-版本号.jar DbUtils工具类的核心类:DbUtils类和QueryRunner类.
DbUtils类:
主要功能是用来进行事务的管理和资源的释放,类中的方法均是静态方法.
void commitAndCloseQuietly(Connection conn):提交事务静默关闭(防止为null值和不抛出异常)连接; void rollbackAndCloseQuietly(Connection conn):回滚事务并静默关闭连接; void closeQuietly(Connection conn,Statement stmt,ResultSet rs):静默释放资源.QueryRunner类:
主要功能是用来执行SQL语句.
构造方法:
public QueryRunner():创建无参的QueryRunner的对象; public QueryRunner(DataSource ds):创建带有数据库连接池的QueryRunner对象.成员方法:
public <T> T query(Connection conn,String sql,ResultSetHandler<T> rsh):给定连接的查询方法; public int update(Connection conn, String sql,Object... params):给定连接的增删改方法; public <T> T query(String sql,ResultSetHandler<T> rsh,Object... params):数据查询方法; public int update(String sql,Object... params):数据的增删改方法;注意事项: 无参的构造方法:一般用于有事务操作的语句: 创建的无参的QueryRunner对象一般和带有连接对象Connection参数的query()方法和update()方法配合使用,Connection对象使用完后需手动进行关闭(可以使用DbUtils类的方法进行关闭). 有参的构造方法:一般用于无事务操作的语句; 创建的有参的QueryRunner对象,其参数是数据库连接池,使用的无Connection参数的query()方法和update()方法,在这两个方法中会自行的去获得连接Connection对象,不需要手动进行连接的关闭,数据操作方法的内部会进行Connection对象归还到连接池中.
query方法和update方法参数解析: ResultSetHandler接口是针对ResultSet结果进行封装的接口类,其内部只定义了handler()方法进行自定义ResultSet结果集数据封装的功能.DbUtils工具类提供了9中ResultSetHandler接口的实现类. Object… params是可变参数,代表的是sql语句中对象的参数,其本质是Object数组.
ResultSetHandler接口有9个实现类:
ArrayHandler:将查询的结果集中的第一条记录封装到Object数组中,结果集中的其他数据丢弃;ArrayListHandler:将查询的结果集中的每一条记录封装到Object数组中后将所有的Object数组再封装到list集合中;BeanHandler:将查询的结果集中的第一条记录封装成javabean对象,结果集中的其他数据丢弃;BeanListHandler:将查询的结果集中的每一条记录封装成javabean对象中后将所有的javabean对象再封装到list集合中;MapHandler:将查询的结果集中的第一条记录封装到map集合中,结果集中的其他数据丢弃;MapListHandler:将查询的结果集中的每一条记录封装到map集合中后将所有的Map集合对象再封装到list集合中;ColumnListHandler:将查询结果的指定列(字段)的值封装到指定存储类型的list集合中;ScalarHandler:将查询结果中的指定列的值封装,单值封装(一般是sql语句聚合函数的结果值),结果一般需要指定数据类型;KeyedHandler:将结果集中的每一条记录封装成Map集合,再将每一条记录封装成的Map集合再封装到另一个Map集合中(该Map集合需指定key是依照结果集中字段).ResultSetHandler接口部分实现类的注意事项:
BeanHandler:javabean类的属性值和查询的结果集中的对应的字段类型相同,名称要相同(否则生成的javabean中的字段值为null);BeanListHandler:javabean类的属性值和查询的结果集中的对应的字段类型相同,名称要相同(否则生成的javabean中的字段值为null);MapHandler:map集合中的key是查询结果集每一列的列名(字段名),value是每条记录对应列(字段)的值,返回值:Map< String, Object>;MapListHandler:map集合中的key是查询结果集每一列的列名(字段名),value是每条记录对应列(字段)的值返回值:List< Map< String, Object >>;ColumnListHandler:当不指定列名时,默认是结果集中的第一列;当不指定结果的数据类型时,默认是Object类型;KeyedHandler:当外层Map集合没有指定key是依照结果集的具体列名时,会默认依照结果集中第一列的列名; 当依照的列名中的记录值有重复时,后面添加进的每条记录的Map集合会覆盖前面的一条记录的Map集合对象.案例代码:
/** * DBUtils工具类的ResultSetHandler结果集封装类测试 * 连接池对象均从JDBCUtils_c3p0工具类获取,也可以使用直接创建连接池对象代替,如下创建连接池对象 * // 创建c3p0的数据库连接池对象 * ComboPooledDataSource cpds = new ComboPooledDataSource(); */ public class My_DBUtils_Demo02 { /** * 测试ArrayHandler封装类 * ArrayHandler:将查询的结果集中的第一条记录封装到Object数组中,结果集中的其他数据丢弃 * @throws SQLException */ @Test public void demo1() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); Object[] objs = queryRunner.query("select * from user", new ArrayHandler()); System.out.println(Arrays.toString(objs)); } /** * 测试ArrayListHandler封装类 * ArrayListHandler:将查询的结果集中的每一条记录封装到Object数组中后将所有的Object数组再封装到list集合中 * @throws SQLException */ @Test public void demo2() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); List<Object[]> list = queryRunner.query("select * from user", new ArrayListHandler()); for (Object[] objects : list) { System.out.println(Arrays.toString(objects)); } } /** * 测试BeanHandler封装类 * BeanHandler:将查询的结果集中的第一条记录封装成javabean对象,结果集中的其他数据丢弃 * 注意事项:javabean类的属性值和查询的结果集中的对应的字段类型相同,名称要相同(否则生成的javabean中的字段值为null) * @throws SQLException */ @Test public void demo3() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); User user = queryRunner.query("select * from user", new BeanHandler<>(User.class)); System.out.println(user); } /** * 测试BeanListHandler封装类 * BeanListHandler:将查询的结果集中的每一条记录封装成javabean对象中后将所有的javabean对象再封装到list集合中 * 注意事项:javabean类的属性值和查询的结果集中的对应的字段类型相同,名称要相同(否则生成的javabean中的字段值为null) * @throws SQLException */ @Test public void demo4() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); List<User> list = queryRunner.query("select * from user", new BeanListHandler<User>(User.class)); System.out.println(list); } /** * 测试MapHandler封装类 * MapHandler:将查询的结果集中的第一条记录封装到map集合中,结果集中的其他数据丢弃 * 注意事项: * map集合中的key是查询结果集每一列的列名(字段名),value是每条记录对应列(字段)的值 * 返回值:Map<String, Object> * @throws SQLException */ @Test public void demo5() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); Map<String, Object> map = queryRunner.query("select * from user", new MapHandler()); System.out.println(map); } /** * 测试MapListHandler封装类 * MapListHandler:将查询的结果集中的每一条记录封装到map集合中后将所有的Map集合对象再封装到list集合中 * map集合中的key是查询结果集每一列的列名(字段名),value是每条记录对应列(字段)的值 * 返回值:List<Map<String, Object>> * @throws SQLException */ @Test public void demo6() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); List<Map<String, Object>> list = queryRunner.query("select * from user", new MapListHandler()); for (Map<String, Object> map : list) { System.out.println(map); } } /** * 测试ColumnListHandler封装类 * ColumnListHandler:将查询结果的指定列(字段)的值封装到指定存储类型的list集合中 * 注意事项:当不指定列名时,默认是结果集中的第一列;当不指定结果的数据类型时,默认是Object类型 * @throws SQLException */ @Test public void demo7() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); List<Object> list = queryRunner.query("select * from user", new ColumnListHandler<Object>("username")); System.out.println(list); } /** * 测试ScalarHandler封装类 * ScalarHandler:将查询结果中的指定列的值封装,单值封装(一般是sql语句聚合函数的结果值),结果一般需要指定数据类型 * @throws SQLException */ @Test public void demo8() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); long count = queryRunner.query("select count(*) from user", new ScalarHandler<Long>()); System.out.println(count); } /** * 测试KeyedHandler封装类 * KeyedHandler:将结果集中的每一条记录封装成Map集合,再将每一条记录封装成的Map集合再封装到另一个Map集合中(该Map集合需指定key是依照结果集中字段) * 当外层Map集合没有指定key是依照结果集的具体列名时,会默认依照结果集中第一列的列名; * 当依照的列名中的记录值有重复时,后面添加进的每条记录的Map集合会覆盖前面的一条记录的Map集合对象 * @throws SQLException */ @Test public void demo9() throws SQLException { QueryRunner queryRunner = new QueryRunner(JDBCUtils_c3p0.getDataSource()); Map<Object, Map<String, Object>> map = queryRunner.query("select * from user", new KeyedHandler<>("username")); System.out.println(map); } }