申明:本文采用自己 C3P0 连接池工具进行测试
自定义的 JDBCUtils 可以获取 Connection:
package com.test.utils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class JDBCUtils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); public static Connection getConnection() { try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException("数据连接获取失败!"); } } public static DataSource getDataSource() { return dataSource; } /** * 释放资源 * @param conn * @param st * @param rs */ public static void colseResource(Connection conn,Statement st,ResultSet rs) { closeResultSet(rs); closeStatement(st); closeConnection(conn); } /** * 释放连接 Connection * @param conn */ public static void closeConnection(Connection conn) { if(conn !=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 conn = null; } /** * 释放语句执行者 Statement * @param st */ public static void closeStatement(Statement st) { if(st !=null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 st = null; } /** * 释放结果集 ResultSet * @param rs */ public static void closeResultSet(ResultSet rs) { if(rs !=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 rs = null; } }
一、DBUtils 简介
1.1 什么是 DBUtils:
Dbutils是由Apache公司提供。
主要是封装了JDBC的代码,简化dao层的操作。帮助java程序员,开发Dao层代码的简单框架。

从上图中可以看出 Connection 的 getMetaData() 方法可以获取用户使用的是什么数据库的 Connecton
当框架知道了数据库的连接是哪个数据库来的,则可以用相应的 SQL 语句进行数据库操作。
2.3 参数的元信息获取
参数的元信息是指:SQL语句中的参数个数、参数所对应的字段类型等。
搞清楚元信息怎么获取之前,要思考一下为什么要获取参数的元信息,看下图为一个正常的 JDBC 数据查询操作:
站在程序员的角度来说,我们当然能看出 SQL 语句中需要多少个参数,因此向 Statement 中 set 参数的时候,传入相应对的占位符位置号:1,2,……
那么如果站在框架的角度,当用户扔一个 sql 语句给框架,让框架给我执行 SQL 语句,
比如 “ select * from student where name like ? and age in (? ,?)” 这样的 sql 语句,框架怎么知道这个 sql 里面有几个占位符呢?
这里就要解释一下:Statement 接口的牛逼之处,只要使用 “?” 作为占位符,通过 getParameterMetaData() 方法就能获取到
sql 语句中的占位符 “?”的个数。来一个极端的例子:
2.4 结果集元信息的获取
获取结果集中的列表的字段个数、类型等信息
总结:
元信息获取方法:DatabaseMetaData metaData = conn.getMetaData(); // 得到数据库的元信息集合
- 参数元信息:ResultSetMetaData metaData = rs.getMetaData(); // 得到结果集的元信息集合
三、DBUtils 工具框架编写
3.1 框架不依赖数据源:
自定义一个 BDAssist 类来模拟 DBUtils,创建一个私有的 DataSource 属性,创建构造函数,当调用者调用的时候,把数据源传进来,
框架拿到数据源的具体实例,框架中使用接口指向这个具体实例,对数据库的一系列操作都是用接口的方法操作,从而摆脱了具体实例的依赖。
private DataSource dataSource;
public DBAssist(DataSource dataSource){
this.dataSource = dataSource;
}
数据的增加、删除、修改、查询分为: DML (增加、删除、修改)和 查询操作
思考用户向框架里仍一条带有参数的 sql 语句,调用 DML 方法的时候,框架就知道怎么操作,并且把用户传递的参数和 sql 语句一一对应起来
那么可以先定义一个 update(String sql,Object...params) 方法,负责把 sql 语句和参数值接收进来,在执行 sql 语句之前,判断参数是否和 sql 语句中
的 “?” 占位符数量一致,如果一致继续对占位符进行设置值,最后执行 sql 语句即可:
/** * 能够执行 DML 语句:insert update delete */ public void update(String sql,Object...params) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { // 与数据源无关 conn = dataSource.getConnection(); st = conn.prepareStatement(sql); int paramsCount = st.getParameterMetaData().getParameterCount(); // 设置参数 if(paramsCount>0) { // 判断参数是否有效且和sql语句中的占位符个数相等 if(params==null || params.length !=paramsCount) { throw new RuntimeException("传递的参数数量不匹配!"); } // 设置 sql 语句中的参数占位符 for (int i = 0; i < paramsCount; i++) { st.setObject(i+1, params[i]); } } st.executeUpdate(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally { colseResource(conn, st, rs); } }
3.2 查询操作
对于查询比较麻烦的是框架执行完 sql 语句之后,需要将查询的结果封装到一个 javabean 里返回给用户。
那么可以定义一个框架的 query(String sql,Object...params) 方法,那不同的 sql 查询语句返回不同的数据对象,框架自己怎么知道需要返回的是什么 javabean呢?
如果是查询一条记录的 sql 语句,如果查到了,框架给用户返回的是一个 javabean 对象。
如果是查询多条记录的 sql 语句,如果查到了,框架给用户返回的是一个 javabean 对象的集合,那么集合需要用什么容器来装呢?
上面这些问题的答案估计只有用户自己知道,好比,框架就是一个生产固型塑料的机器,工人在机器入口放入原材料就可以在机器出口等着成型的塑料。
那工人要求机器生产球型的塑料,机器就能作出球型的塑料,工人要求机器生产矩型的塑料,机器就能作出矩型的塑料,而且两种情况下,原料都是一样的。
说明机器一定是得到了某一个指令,要求机器做出什么形状。所以能看出谁用谁知道框架应该要输出的结果集是什么。
用户1 用框架说我给你指明给我返回 ArrayList 结果集,
用户2 用框架说我给你指明给我返回 HashMap 结果集,
用户3 用框架说我给你指明给我返回 LinkedList 结果集,
……
框架要是只要是一个结果集就都做一个可以返回这个结果集的功能,那估计框架设计者就不干了,做框架就是做标准,总是牵着用户的鼻子走,遇到“奇葩”用户
那不得增加功能到死,所以,框架得面向接口设计,都给我实现一个接口,框架里面我都是使用接口中的方法操作程序的,所以谁来“各种花式”需求,都给我实现接口,那这个框架架爱怎么用就怎么用,自己用的姿势舒服就行。
具体代码实现:
DBAssist 代码实现:
package com.test.DBAssist; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import javax.sql.DataSource; import org.junit.Test; public class DBAssist { private DataSource dataSource; public DBAssist(DataSource dataSource){ this.dataSource = dataSource; } /** * 能够执行 DML 语句:insert update delete */ public void update(String sql,Object...params) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { // 与数据源无关 conn = dataSource.getConnection(); st = conn.prepareStatement(sql); int paramsCount = st.getParameterMetaData().getParameterCount(); // 设置参数 if(paramsCount>0) { // 判断参数是否有效且和sql语句中的占位符个数相等 if(params==null || params.length !=paramsCount) { throw new RuntimeException("传递的参数数量不匹配!"); } // 设置 sql 语句中的参数占位符 for (int i = 0; i < paramsCount; i++) { st.setObject(i+1, params[i]); } } st.executeUpdate(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally { colseResource(conn, st, rs); } } /** * 能够执行 DML 语句:insert update delete */ public Object query(String sql,ResultHandler handler,Object...params) { Connection conn = null; PreparedStatement st = null; ResultSet rs = null; try { // 与数据源无关 conn = dataSource.getConnection(); st = conn.prepareStatement(sql); int paramsCount = st.getParameterMetaData().getParameterCount(); // 设置参数 if(paramsCount>0) { // 判断参数是否有效且和sql语句中的占位符个数相等 if(params==null || params.length !=paramsCount) { throw new RuntimeException("传递的参数数量不匹配!"); } // 设置 sql 语句中的参数占位符 for (int i = 0; i < paramsCount; i++) { st.setObject(i+1, params[i]); } } rs = st.executeQuery(); return handler.handler(rs); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally { colseResource(conn, st, rs); } } /** * 释放资源 * @param conn * @param st * @param rs */ private void colseResource(Connection conn,Statement st,ResultSet rs) { closeResultSet(rs); closeStatement(st); closeConnection(conn); } /** * 释放连接 Connection * @param conn */ private void closeConnection(Connection conn) { if(conn !=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 conn = null; } /** * 释放语句执行者 Statement * @param st */ private void closeStatement(Statement st) { if(st !=null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 st = null; } /** * 释放结果集 ResultSet * @param rs */ private void closeResultSet(ResultSet rs) { if(rs !=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } //等待垃圾回收 rs = null; } }
ResultHandler 接口代码实现:
package com.test.DBAssist; import java.sql.ResultSet; // 抽象策略 public interface ResultHandler { /** * 把结果集中的数据封装到 ResultHandler接口的具体对象中 * @param rs * @return */ Object handler(ResultSet rs); }
ResultHandler 接口的实现类代码实现(BeanHandler 类,返回单个 javabean):
package com.test.DBAssist; import java.lang.reflect.Field; import java.sql.ResultSet; import com.test.daomain.Student; //抽象策略的具体实现 /** * 只对封装一条记录的结果集 * 返回值:封装好的javabean * */ public class BeanHandler implements ResultHandler { private Class clazz; public BeanHandler(Class clazz) { this.clazz = clazz; } @Override public Object handler(ResultSet rs) { try { // 判断是否能查询到结果 if(rs.next()) { Object bean = clazz.newInstance(); // 封装数据 // 要求javabean 的字段名和数据库的列名是一致的 int count = rs.getMetaData().getColumnCount(); for (int i = 0; i < count; i++) { // 获取到结果集的列名称(与javabean 的属性名一致) String columnName = rs.getMetaData().getColumnName(i+1); // 获取列值 Object columnValue = rs.getObject(columnName); // 得到javabean的对应字段 Field field = clazz.getDeclaredField(columnName); field.setAccessible(true); field.set(bean,columnValue); } return bean; } return null; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("封装数据失败!"); } } }
ResultHandler 接口的实现类代码实现(BeanHandler 类,返回 javabean 类型的 list 集合):
package com.test.DBAssist; import java.lang.reflect.Field; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; public class BeanListHandler implements ResultHandler { private Class clazz; public BeanListHandler(Class clazz) { this.clazz = clazz; } @Override public Object handler(ResultSet rs) { try { List list = new ArrayList(); // 判断是否能查询到结果 while(rs.next()) { Object bean = clazz.newInstance(); // 封装数据 // 要求javabean 的字段名和数据库的列名是一致的 int count = rs.getMetaData().getColumnCount(); for (int i = 0; i < count; i++) { // 获取到结果集的列名称(与javabean 的属性名一致) String columnName = rs.getMetaData().getColumnName(i+1); // 获取列值 Object columnValue = rs.getObject(columnName); // 得到javabean的对应字段 Field field = clazz.getDeclaredField(columnName); field.setAccessible(true); field.set(bean,columnValue); } list.add(bean); } return list; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("封装数据失败!"); } } }