DBUtils源码分析

其实,在这篇文章里,我只是分析了dbutis的query的运作流程。

至于类为什么要这样设计,蕴含的设计模式等等高级知识点咱们在下节再探讨。

先看看最简单的DBUtils是如何工作的。

数据库里有一张表,student,里面就三个属性 姓名,学号,出生日期( xm,xh,birth)其中前两个是vchar,birth是date;

package dbutils;

import model.Student;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.ResultSetHandler;
import org.junit.Test;

import java.sql.SQLException;

public class BeanExample {

    @Test
    public void testBean() {

        QueryRunner qr = new QueryRunner(new MyDBSource());

        String sql = "select * from student where xh=?";
        Object params[] = { "02" };
        Student s=null;
        try {
            ResultSetHandler<Student> rsh=new BeanHandler<>(Student.class);
            s =  qr.query(sql,rsh,params);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(s.getXm());

    }

}

其中,MyDBSource就是个提供数据源的工具而已。

public class MyDBSource implements DataSource {

    private static String driverClassName = "com.mysql.jdbc.Driver";
    private static String url = "jdbc:mysql://localhost:3306/webexample";

    private static String userName = "root";
    private static String passWord = "root";

    @Override
    public Connection getConnection() throws SQLException {
        try {
            Class.forName(driverClassName);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return DriverManager.getConnection(url, userName, passWord);

    }
    //....
}

我们再看看BeanHandler的内部。

    //BeanHandler.java
    public BeanHandler(Class<T> type) {
        this(type, ArrayHandler.ROW_PROCESSOR);
    }
    public BeanHandler(Class<T> type, RowProcessor convert) {
        this.type = type;
        this.convert = convert;
    }

    //ArrayHandler.java
    public class ArrayHandler implements ResultSetHandler<Object[]> {
         static final RowProcessor ROW_PROCESSOR = new BasicRowProcessor();
          //....
    }

    //BasicRowProcessor.java
    public class BasicRowProcessor implements RowProcessor {
      private static final BeanProcessor defaultConvert = new BeanProcessor();

      public BasicRowProcessor() {
             this(defaultConvert);
          }
      //...
    }

ok,我们可以看到在BeanHandler中的convert,最终是BeanProcessor类型。

再下来就是我们的重头戏了。

    s =  qr.query(sql,rsh,params);

我们看时序图

1.1 prepareStatement(conn, sql);

       protected PreparedStatement prepareStatement(Connection conn, String sql)
            throws SQLException {
        return conn.prepareStatement(sql);
    }

就是生成prepareStatement

1.2 fillStatement(stmt, params)

大家就是看名字也该知道,fillStatement就是填参数

其核心代码如下:

     for (int i = 0; i < params.length; i++) {
            if (params[i] != null) {
                stmt.setObject(i + 1, params[i]);
            }
        //...
     }

当然fillStatement在核心代码上面还有一块,就是检查sql中的问号数量与params的长度是否相等。

1.3 handle(rs)

     BeanHandler.java
     public T handle(ResultSet rs) throws SQLException {
        return rs.next() ? this.convert.toBean(rs, this.type) : null;
      }

在最开始分析BeanHandler的构造函数时,我们就已经知道了convert是BeanProcessor。

1.3.1 toBean(rs, this.type)

    public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
        PropertyDescriptor[] props = this.propertyDescriptors(type);
        ResultSetMetaData rsmd = rs.getMetaData();
        int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

        return this.createBean(rs, type, props, columnToProperty);
    }

1.3.1.1 propertyDescriptors(type)

这是运用内省(Introspector)获得类型(就是代码里的Student.class)的属性(property)

1.3.1.2 mapColumnsToProperties(rsmd, props)

这一步比较麻烦,为什么说麻烦呢。

数据库里,一张表上有字段,现在这些字段存放在ResultSetMetaData里面

在我们的bean里面,有属性(property),现在存放在props这个数组里。

columnToProperty这里面就放的是字段与属性的对应关系。

例如 columnToProperty[3]=4 就是说ResultSetMetaData里的第三个字段对应于bean的PropertyDescriptor里面的第四个属性。

1.3.1.3 createBean(rs, type, props, columnToProperty)

      //对源码略微有删改
      //但绝对不影响核心思想
      private <T> T createBean(ResultSet rs, Class<T> type,
            PropertyDescriptor[] props, int[] columnToProperty)
            throws SQLException {

        T bean = this.newInstance(type);

        for (int i = 1; i < columnToProperty.length; i++) {

            PropertyDescriptor prop = props[columnToProperty[i]];
            Class<?> propType = prop.getPropertyType();

            Object value = null;
            if(propType != null)
                value = this.processColumn(rs, i, propType);

            this.callSetter(bean, prop, value);
        }

        return bean;
    }

在createBean里面

this.processColumn(rs, i, propType)

就是获得value,那么我们已经知道resultset,还有这个value在resultset中的序列号还有value类型,那么该如何获取呢?

代码我就不贴了,大家自己看源码吧,看上5秒钟就能知道内部逻辑了。

1.3.1.3.1 callSetter(bean, prop, value)

这里面的核心就是下面的代码

                // Don't call setter if the value object isn't the right type
            if (this.isCompatibleType(value, params[0])) {
                setter.invoke(target, new Object[]{value});
            } else {
              throw new SQLException(
                  "Cannot set " + prop.getName() + ": incompatible types, cannot convert "
                  + value.getClass().getName() + " to " + params[0].getName());
                  // value cannot be null here because isCompatibleType allows null
            }

如果value是个String,params却是个double那就得抛出异常了。

时间: 2024-10-13 14:23:46

DBUtils源码分析的相关文章

mybatis 源码分析(四)一二级缓存分析

本篇博客主要讲了 mybatis 一二级缓存的构成,以及一些容易出错地方的示例分析: 一.mybatis 缓存体系 mybatis 的一二级缓存体系大致如下: 首先当一二级缓存同时开启的时候,首先命中二级缓存: 一级缓存位于 BaseExecutor 中不能关闭,但是可以指定范围 STATEMENT.SESSION: 整个二级缓存虽然经过了很多事务相关的组件,但是最终是落地在 MapperStatement 的 Cache 中(Cache 的具体实例类型可以在 mapper xml 的 cach

TeamTalk源码分析之login_server

login_server是TeamTalk的登录服务器,负责分配一个负载较小的MsgServer给客户端使用,按照新版TeamTalk完整部署教程来配置的话,login_server的服务端口就是8080,客户端登录服务器地址配置如下(这里是win版本客户端): 1.login_server启动流程 login_server的启动是从login_server.cpp中的main函数开始的,login_server.cpp所在工程路径为server\src\login_server.下表是logi

Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)

1 背景 还记得前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>中关于透过源码继续进阶实例验证模块中存在的点击Button却触发了LinearLayout的事件疑惑吗?当时说了,在那一篇咱们只讨论View的触摸事件派发机制,这个疑惑留在了这一篇解释,也就是ViewGroup的事件派发机制. PS:阅读本篇前建议先查看前一篇<Android触摸屏事件派发机制详解与源码分析一(View篇)>,这一篇承接上一篇. 关于View与ViewGroup的区别在前一篇的A

HashMap与TreeMap源码分析

1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Java这么久,也写过一些小项目,也使用过TreeMap无数次,但到现在才明白它的实现原理).因此本着"不要重复造轮子"的思想,就用这篇博客来记录分析TreeMap源码的过程,也顺便瞅一瞅HashMap. 2. 继承结构 (1) 继承结构 下面是HashMap与TreeMap的继承结构: pu

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Spark的Master和Worker集群启动的源码分析

基于spark1.3.1的源码进行分析 spark master启动源码分析 1.在start-master.sh调用master的main方法,main方法调用 def main(argStrings: Array[String]) { SignalLogger.register(log) val conf = new SparkConf val args = new MasterArguments(argStrings, conf) val (actorSystem, _, _, _) =

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三)

Solr4.8.0源码分析(22)之 SolrCloud的Recovery策略(三) 本文是SolrCloud的Recovery策略系列的第三篇文章,前面两篇主要介绍了Recovery的总体流程,以及PeerSync策略.本文以及后续的文章将重点介绍Replication策略.Replication策略不但可以在SolrCloud中起到leader到replica的数据同步,也可以在用多个单独的Solr来实现主从同步.本文先介绍在SolrCloud的leader到replica的数据同步,下一篇

zg手册 之 python2.7.7源码分析(4)-- pyc字节码文件

什么是字节码 python解释器在执行python脚本文件时,对文件中的python源代码进行编译,编译的结果就是byte code(字节码) python虚拟机执行编译好的字节码,完成程序的运行 python会为导入的模块创建字节码文件 字节码文件的创建过程 当a.py依赖b.py时,如在a.py中import b python先检查是否有b.pyc文件(字节码文件),如果有,并且修改时间比b.py晚,就直接调用b.pyc 否则编译b.py生成b.pyc,然后加载新生成的字节码文件 字节码对象

LevelDB源码分析--Iterator

我们先来参考来至使用Iterator简化代码2-TwoLevelIterator的例子,略微修改希望能帮助更加容易立即,如果有不理解请各位看客阅读原文. 下面我们再来看一个例子,我们为一个书店写程序,书店里有许多书Book,每个书架(BookShelf)上有多本书. 类结构如下所示 class Book { private: string book_name_; }; class Shelf { private: vector<Book> books_; }; 如何遍历书架上所有的书呢?一种实