抽丝剥茧查看源码, 来自org.apache.commons.dbutils.QueryRunner.class:
第一个核心方法是 batch(Connection conn, boolean closeConn, String sql, Object[][] params) , 该方法使用一条sql语句和二维数组批量执行数据库的update操作, 返回受影响的行的id值.
QueryRunner的Connection需要从Dao中传入, 并调用继承自AbstractQueryRunner封装的prepareStatement方法获得ps对象. 所有有关Connection, PreparedStatement 可能重复使用的代码都被抽取到AbstractQueryRunner类中. 因为是abstract修饰的, 因此不能创建实例对象, 即使没有一个抽象方法, 所有功能必须使用QueryRunner实现.
调用父类方法用给定的对象替换预编译sql语句的参数, 调用addBatch()把编译好的sql语句存入ps对象, 最后执行executeUpdate() 方法.
另一个相似的方法是传入一个不定项参数Object...params 代替 Object[][] params, 这是执行单条update语句的方法.
第二个核心方法是 query语句, 执行查询操作, 返回一个泛型对象 . 这里传入不定项参数 . 在对rSet进行处理的时候, 调用了通过接口ResultSetHandler传入的实例对象的方法 handle(rs), 把rSet中的值转化成对象返回.
ResultSetHandler接口有多种实现类, 包括Bean, BeanList, BeanMap, Map, MapList, ArrayList, List等.
BeanHandler中有一个私有的行处理器 convert, handle(rs)正是调用convert对象的toBeans()方法获得返回值.
接口RowProcessor指向的convert对象来自于Beanhandler的构造方法, 通过ArrayHandler的ROW_PROCESSOR属性, 该属性又来自new BasicRowProcessor(); 而BasicRowProcessor()实现了RowProcessor(), 因此传入的实际是一个RowProcessor对象; 有意思的是在BasicRowProcessor 中convert实际是BeanProcessor() 对象.
按照源码上的解释, BeanProcessor 主要是匹配列名和方法名, 并把结果集中的列转化成Bean类的属性. 子类必须重写方法链以实现自定义的行为. 但是它既不是抽象类, 又没有任何子类, 实际BeanProcessor是通过BasicRowProcessor的属性获得调用, 再由BeanHandler或MapHandler类调用实现的. BeanProcessor基本实现了Bean, BeanList方法, 只有Map等类型是在BasicRowProcessor中新加入的.
toBean方法通过newInstance()方法把Class<T> type转化成了目标 T bean对象 , 调用封装的populateBean(rs, bean)方法, 使用了反射和BeanInfo类, 将列名与bean属性名匹配。属性基于多个因素与列匹配:
1. 类有一个属性与列报名相等(忽略大小写)
2. 通过ResultSet.get方法返回的列的类型可以被转化为属性类型, 返回的值为null时, 属性为默认值.
关键方法在于BeanProcesser的callSetter方法, populateBean方法返回列名, 再通过callSetter方法把每列的值转化为对象属性值, 最后返回Bean对象.
Class<?> firstParam = setter.getParameterTypes()[0]; //该方法返回参数类型
for (PropertyHandler handler : propertyHandlers) {
if (handler.match(firstParam, value)) { //比较方法名与列名
value = handler.apply(firstParam, value); //获得需要的方法.
break;
}
}
最后就是执行invoke方法给属性赋值:
setter.invoke(target, new Object[]{value});
第三个核心方法是List<T> execute(), 执行查询操作, 返回对象的List集合 . 通过继承自父类的prepareCall(conn, sql)方法返回一个(CallableStatement)stmt对象, fillStatement(stmt, params) 用给定的params对象填充sql的参数,
这里通过getResultSet() 返回resultSet结果集, 再通过接口的handler()方法返回对象 T. while循环返回results.
boolean moreResultSets = stmt.execute();
// Handle multiple result sets by passing them through the handler
// retaining the final result
ResultSet rs = null;
while (moreResultSets) {
try {
rs = this.wrap(stmt.getResultSet());
results.add(rsh.handle(rs));
moreResultSets = stmt.getMoreResults();
如果需要返回一个BeanList, 就传入一个new BeanListHandler(Class<T> clazz); 最终调用了BeanProcesser类的toBeanList() 和 populateBean() 方法.
如果需要返回一个Map集合, 就传入一个new MapHandler(Class<T> clazz); 因为BeanProwcesser没有返回Map的方法, 调用的是BasicRowProcessor 的toMap()方法.
总结:
1. 首先创建了一个抽象类, 封装所有要用到的conn到preparedStatement的方法, QueryRunner类需要继承此抽象类
2. 为了能够返回不同的数据类型, 要把接口作为参数, 传入所需的该接口的实例对象
3. 这些实例对象要完成ResultSet到Bean的转化, 要把共有的方法抽取出来封装成一个或多个类, 并调用对应的方法实现.
4. QueryRunner通过调用其他类的方法完成所有工作
哪些方法需要封装:
1.重复使用的代码
2.需要多步完成一项功能的代码块抽取成方法.
3.某些需要抛出异常的代码, 尤其是可能抛出多种异常的情况, 可以单独封装成方法, 便于维护和阅读
* Wrap the <code>ResultSet</code> in a decorator before processing it. This
* implementation returns the <code>ResultSet</code> it is given without any
* decoration.
*
* <p>
* Often, the implementation of this method can be done in an anonymous
* inner class like this:
* </p>
* <pre>
* QueryRunner run = new QueryRunner() {
* protected ResultSet wrap(ResultSet rs) { //有什么意义?
* return StringTrimmedResultSet.wrap(rs);
* }
* };
* </pre>
*
* @param rs
* The <code>ResultSet</code> to decorate; never
* <code>null</code>.
* @return The <code>ResultSet</code> wrapped in some decorator.
*/
protected ResultSet wrap(ResultSet rs) {
return rs;
}