`

Commons DbUtils 源码阅读五

阅读更多

  关于DbUtils,我们深入剖析了对ResultSet解析处理的两个核心类:BeanProcessor和BasicRowProcessor,可以说,这两个类,是对ResultSet的解析有了一个完整的支持。虽然真正做解析工作的是这两个类,但用户对ResultSet的解析是通过接口ResultSetHandler<T>的这个实现类来封装解决的。但要解析的是,我们也得通过SQL语句获取ResultSet对象呀,所以,看看DbUtils是怎么做的。

  一、QueryRunner类,利用可插拨的策略执行SQL查询来处理ResultSets,大致看了一下,该类的重载方法确实有够多。来一类一类的解决:

  1)构造器有多个重载方法,有必要说明解析一下,现列出部分代码:

 

    /**
     *QueryRunner 默认构造器
     */
    public QueryRunner() {
        super();
        ds = null;
    }

    /**
     * 允许Oracle驱动程序的解决方案
     * @param pmdKnownBroken 如果是Oracle drivers,则不支持    ParameterMetaData.getParameterType(int)这个方法;
     * if pmdKnownBroken参数设置为true,则我们不做   
     * ParameterMetaData.getParameterType(int)方法;
     * 如果为false,那将会尝试获取,如果不支持有异常抛出,则不再使用   
   */
    public QueryRunner(boolean pmdKnownBroken) {
        super();
        this.pmdKnownBroken = pmdKnownBroken; 
        ds = null;
    }
    
    /**
     * QueryRunner构造器,Oracle drivers的解决方案. 通过DataSource
     * 获取数据源连接
     * @param ds  数据源,用于获取数据连接Connection
     */
    public QueryRunner(DataSource ds) {
        super();
        this.ds = ds;
    }
    
    /**
     *  QueryRunner构造器,Oracle drivers的解决方案. 通过DataSource
     * 获取数据源连接
     * @param ds  数据源,用于获取数据连接Connection.
     * @param pmdKnownBroken 如果是Oracle drivers,则不支持        ParameterMetaData.getParameterType(int)这个方法;
     * if pmdKnownBroken参数设置为true,则我们不做   
   * ParameterMetaData.getParameterType(int)方法;
     * 如果为false,那将会尝试获取,如果不支持有异常抛出,则不再使用   
     */
    public QueryRunner(DataSource ds, boolean pmdKnownBroken) {
        super();
        this.pmdKnownBroken = pmdKnownBroken;
        this.ds = ds;
    }

   本身这个构造器并没有什么,关键是boolean类型的pmdKnownBroken和DataSource类型的ds。DataSource呢,很明显,是通过它来获取数据库连接,如何程式获取DataSource对象,这得需要借助于commons里的dbcp和pool这两个组件。具体的如何获取,可以参考DBCP组件的官方示例程序,具体网址如下:

   http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/doc/BasicDataSourceExample.java?view=markup  

  这个类中的静态方法setupDataSource就是用来获取数据源的,说个题外话,DBCP和pool这两个组件对于数据源的管理,可谓是鼎鼎大名啊,Spring的数据源管理也是基于该组件,当然了还有另外一个数据源C3P0。关于数据源这一知识点,各位有兴趣的朋友可以参考在下写的“Spring 数据源不同配置 ”,扯远了啊,呵~咱接着说pmdKnownBroken这个变量,虽然现在说起来可能感觉有些抽象。源码的解释是这样的:Oracle的驱动程序不支持ParameterMetaData.getParameterType方法,如果pmdKnownBroken设置为true,则我们甚至不进行尝试处理,而false,我们则会尝试着使用ParameterMetaData.getParameterType方法,如果有异常抛出,则不再使用。

  2)一般来说数据库操作的时候,总是会顺口溜似的:增删改查,所以我们先从QueryRunner类的SQL增加操作说起,看了一下这个类的大概实现,实际上呢,update方法它不仅充当了SQL增加操作,同时也充当了更新和删除的操作,所以,一并了解了吧:

 

 /**
     * 执行一个没有参数的SQL插入、更新或者删除操作
     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement
     * parameters.
     * 
     * @param conn 数据连接The connection to use to run the query.
     * @param sql 要执行的SQL语句The SQL to execute.
     * @return 更新的行数The number of rows updated.
     * @throws SQLException 数据库访问异常if a database access error occurs
     */
    public int update(Connection conn, String sql) throws SQLException {
        return this.update(conn, sql, (Object[]) null);
    }

    /**
     * 执行只有一个参数的SQL插入、修改或者删除操作
     * @param conn 执行查询的数据库连接
     * @param sql 要执行的SQL语句
     * @return 更新的行数
     * @throws SQLException 数据库访问异常
     */
    public int update(Connection conn, String sql, Object param)
        throws SQLException {

        return this.update(conn, sql, new Object[] { param });
    }

    /**
     * 执行指定没有参数的插入、修改或者删除的SQL语句。
     * 数据连接通过DataSource(在构造器中指定)获取。
     * 此连接必须在自动提交模式,否则会导致更新操作不会保存。

     * @param sql 要执行的SQL语句
     * @throws SQLException 数据库访问异常
     * @return 更新的行数
     */
    public int update(String sql) throws SQLException {
        return this.update(sql, (Object[]) null);
    }

    /**
     * 执行指定只有一个参数的插入、修改或者删除的SQL语句。
     * 数据连接通过DataSource(在构造器中指定)获取。
     * 此连接必须在自动提交模式,否则会导致更新操作不会保存。 
     * 
     * @param sql 要执行的SQL语句
     * @param param 参数
     * @throws SQLException  数据库访问异常
     * @return 更新的行数
     */
    public int update(String sql, Object param) throws SQLException {
        return this.update(sql, new Object[] { param });
    }

    /**
     * 执行指定的插入、修改或者删除的SQL语句。
     * 数据连接通过DataSource(在构造器中指定)获取。
     * 此连接必须在自动提交模式,否则会导致更新操作不会保存。 
     * 
     * @param sql 要执行的SQL语句
     * @param params 初始化PreparedStatement参数
     * @throws SQLException 数据库访问异常
     * @return 更新的行数
     */
    public int update(String sql, Object... params) throws SQLException {
        Connection conn = this.prepareConnection();

        try {
            return this.update(conn, sql, params);
        } finally {
            close(conn);
        }
    }

   真是巨多啊!我在每个方法上,都将源码上面的一些说明解释成了中文,各位有兴趣的可以看看。

   挑两个具有代表性的方法来读一下:

      2-1)获取数据库连接,这个呢,源码上面的说明也说了,是通过DataSource来获取的,具体看看prepareConnection()这个方法:

 

    protected Connection prepareConnection() throws SQLException {
        if(this.getDataSource() == null) {
            throw new SQLException("QueryRunner requires a DataSource to be " +
                "invoked in this way, or a Connection should be passed in");
        }
        return this.getDataSource().getConnection();
    }

     这个方法比较的简单,首先是获取数据源实例,如果数据源为空,则抛出异常:必须要有一个DataSource,然后呢,就会获取一个Connection实例返回。这个DataSource实例呢,是在实例化的时候指定的,当然了,我们也可以子类重写这个prepareConnection方法,来实现一个指定的获取数据库连接的方法。

     2-2)

 

     /**
        * 执行一个SQL插入、更新或者删除操作
     * @param conn 执行查询的数据库连接
     * @param sql 要执行的SQL语句
     * @return 更新的行数
     * @throws SQLException 数据库访问异常
     */
    public int update(Connection conn, String sql, Object... params)
        throws SQLException {

        PreparedStatement stmt = null;
        int rows = 0;

        try {
            stmt = this.prepareStatement(conn, sql);//通过Connection和sql获取PreparedStatement实例
            this.fillStatement(stmt, params);
            rows = stmt.executeUpdate();

        } catch (SQLException e) {
            this.rethrow(e, sql, params);

        } finally {
            close(stmt);
        }

        return rows;
    }

 

   来,一步一步的执行这个核心方法,首先,通过prepareStatement这个方法,传入数据库连接和SQL这两个参数

得到一个PreparedStatement对象实例;然后通过fillStatement方法填充参数值,看看具体实现:

 

    /**
       * 通过指定对象填充PreparedStatement的代替参数。
     * @param stmt PreparedStatement to fill
       * @param params 查询替代参数; null也是有效的参数。
     * @throws SQLException 数据库访问异常
     */
    public void fillStatement(PreparedStatement stmt, Object... params)
        throws SQLException {

        if (params == null) {//参数为空,则返回
            return;
        }
       
        ParameterMetaData pmd = null;
        if (!pmdKnownBroken){//false,we try it
            pmd = stmt.getParameterMetaData();//获取关于PreparedStatement 对象中参数的类型和属性信息的对象
            if (pmd.getParameterCount() < params.length) {//如果PreparedStatement需要的参数数量少于指定参数数量,则抛出数量不匹配异常
                throw new SQLException("Too many parameters: expected "
                        + pmd.getParameterCount() + ", was given " + params.length);
            }
        }
        //循环参数
        for (int i = 0; i < params.length; i++) {
            if (params[i] != null) {//如果指定的参数不为空,则指定参数值
                stmt.setObject(i + 1, params[i]);
            } else {
            	// VARCHAR类型可以与许多的驱动工作,而不管真实的列类型.
            	// 奇怪的是,NULL和OTHER与Oracle的驱动不能工作.
                // VARCHAR works with many drivers regardless
                // of the actual column type.  Oddly, NULL and 
                // OTHER don't work with Oracle's drivers.
                int sqlType = Types.VARCHAR;
                if (!pmdKnownBroken) {//false
                    try {
                        sqlType = pmd.getParameterType(i + 1);//获取特定的参数类型
                    } catch (SQLException e) {
                        pmdKnownBroken = true;//如果不支持getParameterType方法,则不再尝试使用
                    }
                }
                stmt.setNull(i + 1, sqlType);//为特定类型赋空值
            }
        }
    }

   我已经对这个方法做了一些必要的说明,实际上呢,最需要强调的,就是pmdKnownBroken参数以及Oracle驱动的关系,pmdKnownBroken这个参数呢,我们已经在构造器那一块说过了,它实际上用于区别Oracle驱动, 说是Oracle驱动不支持getParameterType方法,我是不清楚了,没有使用过,所以没有发言权,但我想这个问题应该会有所解决.另一个批量查询方法batch,主要方法是与update方法类似的,故不再解析。现在呢,主要的方法体功能已经了解完了。

  3)接下来呢,理论上应该是SQL的查询方法解析了,但我看了一下query方法,需要说明的,我们都已经在之前的update方法里拜读过了,唯一不同的是就是多了一个ResultSetHandler<T>参数,之前呢,我有说过ResultSetHandler这个接口,它通过调用handler方法处理ResultSet结果集完成指定类型的转换,本身dbUtils组件呢,提供了众多的ResultSetHandler实现类,它们都位于org.apache.commons.dbutils.handlers的包下,我会在以后的章节中具体解析。

  4)具体来说明一下QueryRunner这个类中的fillStatementWithBean这个方法,在整个组件中暂未用到,但,我想在面向对象的Java开发中,通过指定的bean实例,为SQL语句参数指定bean变量值肯定是会广泛应用的,也就是JavaBean与特定数据表的映射了。Hibernate、JPA等框架能够自动完成对象与关系型数据库的映射,底层的实现也诸如此类吧!

 

    /**
     * 根据bean的属性值填充PerparedStatement的参数
     * @param stmt
     *            待填充值的PreparedStatement
     * @param bean
     *            JavaBean对象
     * @param propertyNames
     *            有序的属性名称数组(这些名字应该有
     *            getters/setters方法匹配);这个属性数组顺序与statement的插入参数顺序匹配
     * @throws SQLException 
     *             数据访问异常
     */
    public void fillStatementWithBean(PreparedStatement stmt, Object bean,
            String... propertyNames) throws SQLException {
        PropertyDescriptor[] descriptors;
        try {
            descriptors = Introspector.getBeanInfo(bean.getClass())
                    .getPropertyDescriptors(); //4-1
        }catch(IntrospectionException e){
            throw new RuntimeException("Couldn't introspect bean " + bean.getClass().toString(), e);
        }
        PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];//4-2
        //参数名与Bean的属性进行比较,确保属性的完整性
        //确保为每个属性名找到在bean中对应的PropertyDescriptor实例
        for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            if (propertyName == null) {//属性列表里的属性不能为空
                throw new NullPointerException("propertyName can't be null: " + i);
            }
            boolean found = false;
            for (int j = 0; j < descriptors.length; j++) {//4-3
                PropertyDescriptor descriptor = descriptors[j];
                if (propertyName.equals(descriptor.getName())) {
                    sorted[i] = descriptor;//此属性在bean中存在,赋于PropertyDescriptor实例
             found = true;
                    break;
                }
            }
            if (!found) {
                throw new RuntimeException("Couldn't find bean property: "
                        + bean.getClass() + " " + propertyName);
            }
        }
        fillStatementWithBean(stmt, bean, sorted);//4-4
    }

    这个方法做的事情是这样的,就是通过指定一个Javabean实例和一个PreparedStatement的参数名数组为PreparedStatement填充对应的参数值。当然了,这些参数名都是JavaBean实例里的属性了。来看看具体的实现过程:

    4-1)通过内省机制获取指定bean实例的PropertyDescriptor[]数组,这样呢,就有了对bean属性的直接操作能力了;

    4-2)根据指定的参数名数组propertyNames,实例化一个PropertyDescriptor[]数组,这个数组,主要的呢,就是存储PreparedStatement指定的参数名对应bean实例中的属性的PropertyDescriptor对象;

    4-3)循环4-1)中Bean实例的PropertyDescriptor数组,如果通过PropertyDescriptor实例获取的属性名与指定的propertyNames相同,则将对应的PeropertyDescriptor实例赋给4-2)中声明的数组;

    4-4)4-1~4-3,这个应该算是真正实现PreparedStatement赋值的前期初始化工作吧,这个fillStatementWithBean的重载方法通过指定stmt中参数的bean实例对应的属性描述数组,下面来看看具体的实现代码吧:

 

    /**
     * 
     * 根据bean的属性值填充PerparedStatement的参数
     * @param stmt
     *            待填充值的PreparedStatement
     * @param bean
     *            JavaBean对象
     * @param properties
     *            指定顺序数组;与PreparedStatement的参数顺序一致
     * @throws SQLException
     *            数据访问异常
     */
    public void fillStatementWithBean(PreparedStatement stmt, Object bean,
            PropertyDescriptor[] properties) throws SQLException {
        Object[] params = new Object[properties.length];//属性值的数组
        for (int i = 0; i < properties.length; i++) {
            PropertyDescriptor property = properties[i];
            Object value = null;
            Method method = property.getReadMethod();//获取属性对应的getter方法
            if (method == null) {
                throw new RuntimeException("No read method for bean property "
                        + bean.getClass() + " " + property.getName());
            }
            try {
                value = method.invoke(bean, new Object[0]);//通过反射调用获取属性值
            } catch (InvocationTargetException e) {
                throw new RuntimeException("Couldn't invoke method: " + method, e);
            } catch (IllegalArgumentException e) {
                throw new RuntimeException("Couldn't invoke method with 0 arguments: " + method, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Couldn't invoke method: " + method, e);
            } 
            params[i] = value;//设置属性值
        }
        fillStatement(stmt, params);
    }

   这个方法我还真的有点懒得再说明下去了,浪费太多面板罗,OK,这个类的解析到此为此吧。

二、QueryLoader类,是一个从一个文件加载查询到一个Map的简单的类。然后,当需要的时候,你从Map中选择一些查询。当然了,这个方法的实现是比较简单的。现看看文件载入的源代码:

 

    /**
     * 载入一个查询命名和SQL值映射的Map集合.
     * 此Map被缓存以便以后相同路径的请求可以返回被缓存的Map
     * Loads a Map of query names to SQL values.  The Maps are cached so a 
     * subsequent request to load queries from the same path will return
     * the cached Map.
     * 
     * @param path The path that the ClassLoader will use to find the file. 
     * ClassLoader通过path查找文件
     * 
     * This is <strong>not</strong> a file system path.  If you had a jarred
     * Queries.properties file in the com.yourcorp.app.jdbc package you would 
     * pass "/com/yourcorp/app/jdbc/Queries.properties" to this method.
     * 这不是一个文件系统路径。如果你有一个Queries.properties文件在com.yourcorp.app.jdbc这个包下,
     * 那么你应该传递"/com/yourcorp/app/jdbc/Queries.properties"参数到这个方法.
     * 
     * @throws IOException if a file access error occurs
     * @throws IllegalArgumentException if the ClassLoader can't find a file at
     * the given path.
     * @return Map of query names to SQL values
     */
    public synchronized Map<String,String> load(String path) throws IOException {

        Map<String,String> queryMap = (Map<String,String>) this.queries.get(path);

        if (queryMap == null) {
            queryMap = this.loadQueries(path);
            this.queries.put(path, queryMap);
        }

        return queryMap;
    }

    这个方法加入了同步锁机制,所以是线程安全的,这个方法需要注意的一点就是传入的路径,因为是通过ClassLoader载入,所以,传入的路径是绝对路径名。首先呢,先从本地的Map集合queries拿到路径名里对应的集合,如果为空,则说明没有缓存对不对,OK,没有就加呗,来loadQueries方法:

 

    /**
     * Loads a set of named queries into a Map object.  This implementation
     * reads a properties file at the given path.
     * 
     * 加载命名查询集到一个Map对象中.
     * 这个实现用于读取给定路径的Properties文件
     * 
     * @param path The path that the ClassLoader will use to find the file.
     * 		ClassLoader使用指定的path去查找file
     * @throws IOException file访问异常
     * @throws IllegalArgumentException ClassLoader查找不到指定路径的文件
     * @since DbUtils 1.1
     * @return Map of query names to SQL values 查询名称到SQL值的映射集合
     */
    @SuppressWarnings("unchecked")
    protected Map<String,String> loadQueries(String path) throws IOException {
        // Findbugs flags getClass().getResource as a bad practice; maybe we should change the API?
    	
        InputStream in = getClass().getResourceAsStream(path);//获取指定文件的流对象

        if (in == null) {
            throw new IllegalArgumentException(path + " not found.");
        }

        Properties props = new Properties();
        props.load(in);

        // Copy to HashMap for better performance
        return new HashMap(props);
    }

    哝,有没有?!通过ClassLoader载入指定文件流对象,如果为空,则会抛出找不到文件的异常。否则呢,载入Properties文件并以HashMap返回。

   载入了未缓存的properties文件,那么,存一个:

 

 this.queries.put(path, queryMap);

   完成了载入、缓存,缓存这个东西呢是要占内存的,所以呢,不要缓存太多或者大的对象,除非有必要,建议各位用完就remove掉:

 

    /**
     * Removes the queries for the given path from the cache.
     * 	从缓存中删除指定的路径
     * @param path The path that the queries were loaded from.
     * 	 queries载入的路径
     */
    public synchronized void unload(String path){
        this.queries.remove(path);
    }

   好了,DbUtils这两个类的解析就到此为此吧,回头看看,发现DbUtils组件的一些主要的类都已经解析完成了,继续努力!

 

 

 

 

分享到:
评论

相关推荐

    commons-dbutils-1.7-API文档-中文版.zip

    赠送源代码:commons-dbutils-1.7-sources.jar; 赠送Maven依赖信息文件:commons-dbutils-1.7.pom; 包含翻译后的API文档:commons-dbutils-1.7-javadoc-API文档-中文(简体)版.zip; Maven坐标:commons-dbutils:...

    Commons-dbutils1.7 jar包.rar

    commons-dbutils包是Apache开源组织提供的用于操作数据库的工具包。简单来讲,这个工具包就是用来更加方便我们操作数据库的,最近工作中使用了一下,感觉确实方便很多,基本告别自己封装JDBC代码对数据库进行增删改...

    Commons DbUtils源码阅读之实例及测试应用

    NULL 博文链接:https://cuics-100.iteye.com/blog/920565

    commons-dbutils-1.7-API文档-中英对照版.zip

    赠送源代码:commons-dbutils-1.7-sources.jar; 赠送Maven依赖信息文件:commons-dbutils-1.7.pom; 包含翻译后的API文档:commons-dbutils-1.7-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:commons-...

    commons-dbutils-1.6的jar包、源码、文档说明.zip

    commons-dbutils-1.6的jar包、源码、文档说明.zip

    commons-dbutils-1.4 bin+src

    commons-dbutils-1.4 jar包和源码文件,查看源码非常方便,欢迎下载

    apache-commons下源码大放送

    apache-commons下全部官方源码和官方API文档,其中有: commons-beanutils-1.8.0 commons-codec commons-collections commons-dbcp commons-dbutils commons-fileupload commons-io commons-lang commons-lang3 ...

    commons-dbutils-1.7-src.zip

    dao轻量级框架dbutils的源码

    commons-dbutils组件包与源码

    commons-dbutils组件包与源码,人个收集,提供给大家共同学习,共同进步。

    Dbutils项目实例

    DBUtils封装了对JDBC的操作 简化了JDBC操作 可以少写代码 org apache commons dbutils DbUtils 关闭链接等操作 QueryRunner 进行查询的操作 org apache commons dbutils handlers ArrayHandler :将ResultSet中第一...

    apache commons jar(commons所有的jar包,从官网下载提供.zip

    apache commons jar(commons所有的jar包,从官网下载提供给大家) 因为涉及jar太多,包括有src源代码,只需要3分,希望大家理解,我也是从官网花了很长时间才一个一个下完,需要的请自取。全部是zip文件,每个对应的...

    commons-dbutils使用简单范例--简化jdbc编程

    NULL 博文链接:https://gnnan.iteye.com/blog/1170042

    commons-dbutils-1.7-bin.zip

    dao层的一个轻量级框架,使用非常方便,是apache的一个开源项目,源码我也上传了

    org.apache.commons 的 jar 包 源码

    org.apache.commons 的 jar 包 源码 org.apache.commons 的 jar 包 源码 org.apache.commons 的 jar 包 源码

    DbUtils扩展源码

    ApacheCommos的DbUtils是一个简单好用的轻量级的数据库操作工具,该项目的主页是:http://commons.apache.org/dbutils/,关于它的信息可以从那里获取. dbutils可以把查询出来的结果集映射成Bean的List,这是个很有用的...

    commons-dbutils-1.7.jar中文-英文对照文档.zip

    源代码下载地址:【***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: 中文-英文对照文档,中英对照文档,java,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,中文API文档,手册,...

    最新版commons-dbutils-1.6带源码

    官网下载的最新的2017年commons jar包资源,

    apache-commons源码及jar文件

    DbUtils 是一个 JDBC helper 类库,完成数据库任务的简单的资源清除代码. Digester Commons-Digester 是一个 XML-Java对象的映射工具,用于解析 XML配置文件. Discovery Commons-Discovery 提供工具来定位资源 ...

    Dbutils学习源码总结

    apache下面有很多值得学习的开源项目,尤其是commons系列,在此,特封装了其组织下的dbutils根据,方便了喜欢使用sql开发的java朋友,里面有各种实用的封装类和对数据库操作的接口,欢迎下载!

    apache commons jar(commons所有的jar包,从官网下载提供给大家)

    因为涉及jar太多,包括有src源代码,只需要3分,希望大家理解,我也是从官网花了很长时间才一个一个下完,需要的请自取。全部是zip文件,每个对应的会有一个bin和一个src源文件。 apache-sanselan-incubating-0.97-...

Global site tag (gtag.js) - Google Analytics