Apache dbutils源碼分析

本文對apache dbutils項目的源碼進(jìn)行分析,目錄如下:

1、基本使用

2、DBUtilsy源碼分析

2.1 QueryRunner

2.2 AbstractQueryRunner

2.3 結(jié)果集處理

3、總結(jié)


1、基本使用

DBUtils 官方示例

從上述使用示例中可以看出DBUtils的使用時序如下:

DBUtils運行時序圖
DBUtils運行時序圖

上圖中的第4步有誤,就是返回而已

可以看出dbutils核心的就兩部分:

  1. 提供的多樣的API及對SQL參數(shù)的封裝處理
  2. 結(jié)果集轉(zhuǎn)換到JavaBean的處理

接下來對源碼進(jìn)行分析

2、DBUtils源碼分析

對DBUtils代碼的分析按上述執(zhí)行過程進(jìn)行

2.1、QueryRunner

QueryRunner繼承于AbstractQueryRunner類,提供了多樣的增刪改查接口,大多接口都是調(diào)用到最核心的幾個方法上,本文僅取兩個典型的方法:

  • <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
  • <T> T insertBatch(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object[][] params)

來做分析,其它的大致相同,可參考源碼。
另外,對AbstractQueryRunner的分析,在下面專門進(jìn)行。

/**
 * Calls query after checking the parameters to ensure nothing is null.
 * @param conn The connection to use for the query call.
 * @param closeConn True if the connection should be closed, false otherwise.
 * @param sql The SQL statement to execute.
 * @param params An array of query replacement parameters.  Each row in
 * this array is one set of batch replacement values.
 * @return The results of the query.
 * @throws SQLException If there are database or parameter errors.
 */
private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
        throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    }

    if (sql == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null SQL statement");
    }

    if (rsh == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null ResultSetHandler");
    }

    PreparedStatement stmt = null;
    ResultSet rs = null;
    T result = null;

    try {
        // 對PreparedStatement對象進(jìn)行初始化
        stmt = this.prepareStatement(conn, sql);
        
        // 將參數(shù)填充到語句中,該函數(shù)是在AbstractQueryRunner內(nèi)實現(xiàn)的,下面做分析
        this.fillStatement(stmt, params);
        
        // 對結(jié)果集進(jìn)行包裝
        rs = this.wrap(stmt.executeQuery());
        // 對結(jié)果集進(jìn)行轉(zhuǎn)換處理
        result = rsh.handle(rs);

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

    } finally {
        try {
            close(rs);
        } finally {
            close(stmt);
            if (closeConn) {
                close(conn);
            }
        }
    }

    return result;
}

批量插入操作

/**
 * Executes the given batch of INSERT SQL statements.
 * @param conn The connection to use for the query call.
 * @param closeConn True if the connection should be closed, false otherwise.
 * @param sql The SQL statement to execute.
 * @param rsh The handler used to create the result object from
 * the <code>ResultSet</code> of auto-generated keys.
 * @param params The query replacement parameters.
 * @return The result generated by the handler.
 * @throws SQLException If there are database or parameter errors.
 * @since 1.6
 */
private <T> T insertBatch(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object[][] params)
        throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    }

    if (sql == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null SQL statement");
    }

    if (params == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
    }

    PreparedStatement stmt = null;
    T generatedKeys = null;
    try {
        // Statement.RETURN_GENERATED_KEYS 表示獲取插入SQL語句的ID值
        // 設(shè)定為自增長id方式
        stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS);
     
        // 遍歷參數(shù)列表,逐條參數(shù)信息填充
        for (int i = 0; i < params.length; i++) {
            // 將參數(shù)填充到語句中,該函數(shù)是在AbstractQueryRunner內(nèi)實現(xiàn)的,下面做分析
            this.fillStatement(stmt, params[i]);
            stmt.addBatch();
        }
        // 批量執(zhí)行
        stmt.executeBatch();
        // 獲取自增長ID
        ResultSet rs = stmt.getGeneratedKeys();
        generatedKeys = rsh.handle(rs);

    } catch (SQLException e) {
        this.rethrow(e, sql, (Object[])params);
    } finally {
        close(stmt);
        if (closeConn) {
            close(conn);
        }
    }

    return generatedKeys;
}

Apache-dbutils還提供了線程安全的SQL執(zhí)行類 AsyncQueryRunner,其內(nèi)部最終也是通過QueryRunner來實現(xiàn),此處就不做分析了。

2.2 AbstractQueryRunner

AbstractQueryRunner是一個抽象類,是 QueryRunner和 AsyncQueryRunner 的基類,主要提供了兩方面的東西

  • 對SQL語句參數(shù)進(jìn)行填充
  • 關(guān)閉數(shù)據(jù)庫連接,SQL 參數(shù)的準(zhǔn)備等

對參數(shù)填充的方法fillStatement(stmt,params)

/**
 * Fill the <code>PreparedStatement</code> replacement parameters with the
 * given objects.
 *
 * @param stmt
 *            PreparedStatement to fill
 * @param params
 *            Query replacement parameters; <code>null</code> is a valid
 *            value to pass in.
 *            null值是合法參數(shù)
 * @throws SQLException
 *             if a database access error occurs
 */
 /**
  * fillStatement方法的處理流程:
  * fillStatement的主要作用就是將需要在SQL中添加的參數(shù)進(jìn)行填充到準(zhǔn)備語句中去。
  * 在填充過程中處理:
  *  1、通過參數(shù)個數(shù)做校驗
  *     通過獲取準(zhǔn)備語句的參數(shù)元信息與參數(shù)值個數(shù)做比對,不合法則拋出SQL異常;
  *  2、調(diào)用準(zhǔn)備語句方法(setObject(...))逐個參數(shù)值按序設(shè)定
  *  3、對參數(shù)值為null的處理
  *     參數(shù)值為null時,主要正確設(shè)置null對應(yīng)參數(shù)的SQL類型。
  *     通過參數(shù)元信息中獲取對應(yīng)位置的參數(shù)類型信息,將此信息設(shè)置;否則就使用默認(rèn)的vchar類型設(shè)定
  */
public void fillStatement(PreparedStatement stmt, Object... params)
        throws SQLException {

    // check the parameter count, if we can
    /* ParameterMetaData類用來表示PreparedStatement實例的參數(shù)相關(guān)信息,
     * 包括參數(shù)個數(shù),類型等一些屬性。這些屬性信息通過 
     * ParameterMetaData getParameterMetaData() throws SQLException;  
     * 方法獲取。ParameterMetaData的使用參考官方API文檔即可。
     */
    ParameterMetaData pmd = null;
    
    /** pmdKnownBroken是本類的一個booleanl類型的成員變量,用來區(qū)分一個JDBC驅(qū)動是否支持
     *  {@link ParameterMetaData#getParameterType(int) };的操作。
     *  設(shè)置為true,則表示不支持;false 則表示支持
     */
    if (!pmdKnownBroken) {
        // 獲取參數(shù)相關(guān)的元數(shù)據(jù)信息,包含參數(shù)個數(shù)和類型等信息
        pmd = stmt.getParameterMetaData();
        int stmtCount = pmd.getParameterCount();
        int paramsCount = params == null ? 0 : params.length;

        if (stmtCount != paramsCount) {
            throw new SQLException("Wrong number of parameters: expected "
                    + stmtCount + ", was given " + paramsCount);
        }
    }

    // nothing to do here
    if (params == null) {
        return;
    }

    for (int i = 0; i < params.length; i++) {
        if (params[i] != null) {
            stmt.setObject(i + 1, params[i]);
        } else {
            // 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) {
                try {
                    /*
                     * It's not possible for pmdKnownBroken to change from
                     * true to false, (once true, always true) so pmd cannot
                     * be null here.
                     */
                    sqlType = pmd.getParameterType(i + 1);
                } catch (SQLException e) {
                    pmdKnownBroken = true;
                }
            }
            // 對null參數(shù)值的處理
            stmt.setNull(i + 1, sqlType);
        }
    }
}

DBUtils還提供了一種根據(jù)java實體和指定屬性名的方式來進(jìn)行參數(shù)填充

/**
 * Fill the <code>PreparedStatement</code> replacement parameters with the
 * given object's bean property values.
 * 通過bean以及要執(zhí)行的屬性名稱來進(jìn)行參數(shù)填充
 * 
 * @param stmt
 *            PreparedStatement to fill
 * @param bean
 *            A JavaBean object
 * @param propertyNames
 *            An ordered array of property names (these should match the
 *            getters/setters); this gives the order to insert values in the
 *            statement
 * @throws SQLException
 *             If a database access error occurs
 */
public void fillStatementWithBean(PreparedStatement stmt, Object bean,
        String... propertyNames) throws SQLException {
    PropertyDescriptor[] descriptors;
    try {
        // 獲取實體的屬性集合
        descriptors = Introspector.getBeanInfo(bean.getClass())
                .getPropertyDescriptors();
    } catch (IntrospectionException e) {
        throw new RuntimeException("Couldn't introspect bean "
                + bean.getClass().toString(), e);
    }
    PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
    // 遍歷傳入的屬性列表,匹配對應(yīng)的實體屬性,存儲到sorted內(nèi)
    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++) {
            PropertyDescriptor descriptor = descriptors[j];
            // 此處直接進(jìn)行比較                 
            if (propertyName.equals(descriptor.getName())) {
                /**
                 *  此處直接將元素屬性名復(fù)制,作為表字段名稱;如果要進(jìn)行實體屬性名(駝峰命名)
                 *  與數(shù)據(jù)庫表字段名映射,可在下面代碼中處理即可。
                 */
                sorted[i] = descriptor;
                found = true;
                break;
            }
        }
        if (!found) {
            throw new RuntimeException("Couldn't find bean property: "
                    + bean.getClass() + " " + propertyName);
        }
    }
    // 到此,只是將實體屬性與給定的進(jìn)行過濾提取了一遍,而真正的值還在實體bean內(nèi)部
    fillStatementWithBean(stmt, bean, sorted);
}
   

上面調(diào)用了fillStatementWithBean(PreparedStatement stmt, Object bean,
PropertyDescriptor[] properties)來獲取屬性值并進(jìn)行填充

/**
 * Fill the <code>PreparedStatement</code> replacement parameters with the
 * given object's bean property values.
 *
 * @param stmt
 *            PreparedStatement to fill
 * @param bean
 *            a JavaBean object
 * @param properties
 *            an ordered array of properties; this gives the order to insert
 *            values in the statement
 * @throws SQLException
 *             if a database access error occurs
 *             
 * 該方法將根據(jù)傳入的實體屬性集合通過反射bean獲取屬性值來進(jìn)行參數(shù)填充
 */
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();
        if (method == null) {
            throw new RuntimeException("No read method for bean property "
                    + bean.getClass() + " " + property.getName());
        }
        try {
            // 通過回調(diào)屬性讀方法獲取到屬性值
            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;
    }
    // 參數(shù)填充
    fillStatement(stmt, params);
}

AbstractQueryRunner類還提供了一個拋出SQL、參數(shù)信息的異常方法:

/**
 * Throws a new exception with a more informative error message.
 *
 * @param cause
 *            The original exception that will be chained to the new
 *            exception when it's rethrown.
 *
 * @param sql
 *            The query that was executing when the exception happened.
 *
 * @param params
 *            The query replacement parameters; <code>null</code> is a valid
 *            value to pass in.
 *
 * @throws SQLException
 *             if a database access error occurs
 */
protected void rethrow(SQLException cause, String sql, Object... params)
        throws SQLException {
    // 獲取異常信息
    String causeMessage = cause.getMessage();
    if (causeMessage == null) {
        causeMessage = "";
    }
    StringBuffer msg = new StringBuffer(causeMessage);

    // 添加出現(xiàn)異常的SQL和參數(shù)信息
    msg.append(" Query: ");
    msg.append(sql);
    msg.append(" Parameters: ");

    if (params == null) {
        msg.append("[]");
    } else {
        msg.append(Arrays.deepToString(params));
    }

    // 構(gòu)建并拋出異常
    SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
            cause.getErrorCode());
    e.setNextException(cause);

    throw e;
}

2.3 結(jié)果集處理

ResultSetHandler接口,將ResultSet轉(zhuǎn)換為一個Object對象

/**
 * Implementations of this interface convert ResultSets into other objects.
 *
 * @param <T> the target type the input ResultSet will be converted to.
 */
public interface ResultSetHandler<T> {

    /**
     * Turn the <code>ResultSet</code> into an Object.
     * 將ResultSet轉(zhuǎn)換為一個Object對象
     * @param rs The <code>ResultSet</code> to handle.  It has not been touched
     * before being passed to this method.
     *
     * @return An Object initialized with <code>ResultSet</code> data. It is
     * legal for implementations to return <code>null</code> if the
     * <code>ResultSet</code> contained 0 rows.
     *
     * @throws SQLException if a database access error occurs
     */
    T handle(ResultSet rs) throws SQLException;

}

其實現(xiàn)有:

  • BeanHandler:將結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)換成一個JavaBean實例
  • BeanListHandler:將結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)成一個JavaBean實例,存放到List中
  • BeanMapHandler:將結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)成一個JavaBean實例,并指定某一列作為Key,存放到Map中
  • AbstractKeyedHandler:將每一行記錄轉(zhuǎn)換成一個鍵值對
  • AbstractListHandler:將結(jié)果集轉(zhuǎn)換到一個List列表內(nèi)
  • ArrayHandler:結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)換成Object數(shù)組
  • ArrayListHandler:把結(jié)果集中的每一行數(shù)據(jù)都轉(zhuǎn)換成一個Object[]數(shù)組,再存放在List中
  • ColumnListHandler:將結(jié)果集中某一列的數(shù)據(jù)存放到List中

等等,提供了各種各樣的實現(xiàn)。
此處,僅對BeanHandler類的實現(xiàn)做分析,其它大多雷同,就不分析了。

/**
 * <code>ResultSetHandler</code> implementation that converts the first
 * <code>ResultSet</code> row into a JavaBean. This class is thread safe.
 * 將結(jié)果集中的第一行數(shù)據(jù)轉(zhuǎn)換成一個JavaBean實例。
 * 
 * @param <T> the target bean type
 * @see org.apache.commons.dbutils.ResultSetHandler
 */
public class BeanHandler<T> implements ResultSetHandler<T> {

    /**
     * The Class of beans produced by this handler.
     */
    private final Class<T> type;

    /**
     * The RowProcessor implementation to use when converting rows
     * into beans.
     * 真正的轉(zhuǎn)換器,基本所有的結(jié)果集的處理都在這個類里面,接下來做重點分析
     */
    private final RowProcessor convert;

    /**
     *  有兩個構(gòu)造方法省略......
     */

    /**
     * Convert the first row of the <code>ResultSet</code> into a bean with the
     * <code>Class</code> given in the constructor.
     * @param rs <code>ResultSet</code> to process.
     * @return An initialized JavaBean or <code>null</code> if there were no
     * rows in the <code>ResultSet</code>.
     *
     * @throws SQLException if a database access error occurs
     * @see org.apache.commons.dbutils.ResultSetHandler#handle(java.sql.ResultSet)
     */
    @Override
    public T handle(ResultSet rs) throws SQLException {
        // 通過轉(zhuǎn)換器來實現(xiàn)
        return rs.next() ? this.convert.toBean(rs, this.type) : null;
    }

}

很容易看出dbutils提供了一個對RowProcessor的基本實現(xiàn):BasicRowProcessor
我們來看上面的轉(zhuǎn)換器中的實現(xiàn):

public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
    return this.convert.toBean(rs, type);
}

在BasicRowProcessor內(nèi)又通過調(diào)用BeanProcessor的方法來實現(xiàn),專門處理對JavaBean的轉(zhuǎn)換
根據(jù)結(jié)果集和類類型來創(chuàng)建實體類,經(jīng)歷以下處理過程:

  1. 獲取bean的所有類型
  2. 獲取結(jié)果集中的類型和值
  3. 然后將結(jié)果集中的類型與Bean中的類型匹配(涉及到表字段的命名與實體屬性名轉(zhuǎn)換問題)
  4. 通過實體bean的setter方法反射設(shè)定
public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {

    // 獲得JavaBean的屬性
    PropertyDescriptor[] props = this.propertyDescriptors(type);

    // 獲得結(jié)果集中的字段屬性 
    // ResultSetMetaData為結(jié)果集元數(shù)據(jù)類,使用可參考官方API
    ResultSetMetaData rsmd = rs.getMetaData();

    //  columnToProperty[3]=4 就是說ResultSetMetaData里的第三個字段
    //   對應(yīng)于bean的PropertyDescriptor里面的第四個屬性
    // 數(shù)組中的數(shù)字是拿 結(jié)果集中的字段屬性去按個匹配Bean中的屬性,得到合法屬性在結(jié)果集中的索引序列,
    // 后續(xù)轉(zhuǎn)換bean時都以此為準(zhǔn),實際上就是將表中存在的字段在bean中不存在的過濾出去了,bean中不存在的
    // 是無法屬性設(shè)定的
    int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

    // 創(chuàng)建實體對象
    return this.createBean(rs, type, props, columnToProperty);
}
// 將結(jié)果集中存在的屬性在實體bean 中進(jìn)行過濾,并返回存在的索引
// 為后續(xù)轉(zhuǎn)換bean時提供依據(jù),實際上就是將表中存在的字段在bean中不存在的過濾出去了,bean中不存在的
// 是無法屬性設(shè)定的
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
            PropertyDescriptor[] props) throws SQLException {

    int cols = rsmd.getColumnCount();
    
    // 定義并初始化數(shù)組為-1
    int[] columnToProperty = new int[cols + 1];
    Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

    // 逐個遍歷結(jié)果集屬性
    for (int col = 1; col <= cols; col++) {
        
        // 獲得字段名稱
        String columnName = rsmd.getColumnLabel(col);
        if (null == columnName || 0 == columnName.length()) {
          columnName = rsmd.getColumnName(col);
        }
        String propertyName = columnToPropertyOverrides.get(columnName);
        if (propertyName == null) {
            propertyName = columnName;
        }
        
        // 遍歷Bean屬性
        for (int i = 0; i < props.length; i++) {

            // 忽略大小寫比較
            if (propertyName.equalsIgnoreCase(props[i].getName())) {
                columnToProperty[col] = i;
                break;
            }
        }
    }

    return columnToProperty;
}

已獲得足夠多的信息,開始創(chuàng)建實體Bean

/**
 * Creates a new object and initializes its fields from the ResultSet.
 * 創(chuàng)建實體類,并根據(jù)結(jié)果集初始化其屬性值
 * @param <T> The type of bean to create
 * @param rs The result set.
 * @param type The bean type (the return type of the object).
 * @param props The property descriptors.
 * @param columnToProperty The column indices in the result set.
 * @return An initialized object.
 * @throws SQLException if a database error occurs.
 */
private <T> T createBean(ResultSet rs, Class<T> type,
        PropertyDescriptor[] props, int[] columnToProperty)
        throws SQLException {

    // 創(chuàng)建對象
    T bean = this.newInstance(type);

    // 按提取好的合法的屬性集逐個設(shè)定
    for (int i = 1; i < columnToProperty.length; i++) {

        if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
            continue;
        }

        // 提取Bean屬性
        PropertyDescriptor prop = props[columnToProperty[i]];
        Class<?> propType = prop.getPropertyType();

        Object value = null;
        if(propType != null) {
            
            // 從結(jié)果集中根據(jù)屬性獲得值
            value = this.processColumn(rs, i, propType);

            // 原生類型的值為空,則設(shè)定默認(rèn)值
            if (value == null && propType.isPrimitive()) {
                // primitiveDefaults中通過靜態(tài)塊代碼已將原生類型的默認(rèn)值初始好了
                value = primitiveDefaults.get(propType);
            }
        }
        // 設(shè)定屬性值
        this.callSetter(bean, prop, value);
    }

    return bean;
}

從結(jié)果集中根據(jù)屬性獲得值

protected Object processColumn(ResultSet rs, int index, Class<?> propType)
    throws SQLException {

    // index為結(jié)果集中的索引, proType為該索引對應(yīng)的數(shù)據(jù)轉(zhuǎn)換為的目的JavaBean內(nèi)的屬性
    // 根據(jù)索引和類型,將提取到的結(jié)果值做轉(zhuǎn)換并返回
    if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
        return null;
    }

    if (propType.equals(String.class)) {
        return rs.getString(index);

    } else if (
        propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
        return Integer.valueOf(rs.getInt(index));

    } else if (
        propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
        return Boolean.valueOf(rs.getBoolean(index));

    } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
        return Long.valueOf(rs.getLong(index));

    } else if (
        propType.equals(Double.TYPE) || propType.equals(Double.class)) {
        return Double.valueOf(rs.getDouble(index));

    } else if (
        propType.equals(Float.TYPE) || propType.equals(Float.class)) {
        return Float.valueOf(rs.getFloat(index));

    } else if (
        propType.equals(Short.TYPE) || propType.equals(Short.class)) {
        return Short.valueOf(rs.getShort(index));

    } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
        return Byte.valueOf(rs.getByte(index));

    } else if (propType.equals(Timestamp.class)) {
        return rs.getTimestamp(index);

    } else if (propType.equals(SQLXML.class)) {
        return rs.getSQLXML(index);

    } else {
        return rs.getObject(index);
    }
}

設(shè)置屬性值

// 調(diào)用反射調(diào)用JavaBean的setter方法,來注入結(jié)果集中的值
private void callSetter(Object target, PropertyDescriptor prop, Object value)
        throws SQLException {

    // 獲取setter方法
    Method setter = prop.getWriteMethod();

    if (setter == null) {
        return;
    }

    Class<?>[] params = setter.getParameterTypes();
    try {
        // convert types for some popular ones
        // 對幾種特殊類型做的轉(zhuǎn)換處理
        if (value instanceof java.util.Date) {
            final String targetType = params[0].getName();
            if ("java.sql.Date".equals(targetType)) {
                value = new java.sql.Date(((java.util.Date) value).getTime());
            } else
            if ("java.sql.Time".equals(targetType)) {
                value = new java.sql.Time(((java.util.Date) value).getTime());
            } else
            if ("java.sql.Timestamp".equals(targetType)) {
                Timestamp tsValue = (Timestamp) value;
                int nanos = tsValue.getNanos();
                value = new java.sql.Timestamp(tsValue.getTime());
                ((Timestamp) value).setNanos(nanos);
            }
        } else
        if (value instanceof String && params[0].isEnum()) {
            value = Enum.valueOf(params[0].asSubclass(Enum.class), (String) value);
        }

        // Don't call setter if the value object isn't the right type
        // 對參數(shù)類型和值類型做匹配檢查
        if (this.isCompatibleType(value, params[0])) {
            
            // 調(diào)用set方法 設(shè)定值
            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
        }

    } catch (IllegalArgumentException e) {
        throw new SQLException(
            "Cannot set " + prop.getName() + ": " + e.getMessage());

    } catch (IllegalAccessException e) {
        throw new SQLException(
            "Cannot set " + prop.getName() + ": " + e.getMessage());

    } catch (InvocationTargetException e) {
        throw new SQLException(
            "Cannot set " + prop.getName() + ": " + e.getMessage());
    }
}

對參數(shù)的類型和值做匹配檢查

// 對參數(shù)的類型和值做匹配檢查
private boolean isCompatibleType(Object value, Class<?> type) {
    // Do object check first, then primitives
    if (value == null || type.isInstance(value)) {
        return true;

    } else if (type.equals(Integer.TYPE) && value instanceof Integer) {
        return true;

    } else if (type.equals(Long.TYPE) && value instanceof Long) {
        return true;

    } else if (type.equals(Double.TYPE) && value instanceof Double) {
        return true;

    } else if (type.equals(Float.TYPE) && value instanceof Float) {
        return true;

    } else if (type.equals(Short.TYPE) && value instanceof Short) {
        return true;

    } else if (type.equals(Byte.TYPE) && value instanceof Byte) {
        return true;

    } else if (type.equals(Character.TYPE) && value instanceof Character) {
        return true;

    } else if (type.equals(Boolean.TYPE) && value instanceof Boolean) {
        return true;

    }
    return false;

}

到此,整個dbutils的大多數(shù)的源碼已經(jīng)剖析完了,下面對此做個總結(jié)。

3、總結(jié)

dbutils對封裝了jdbc的操作,簡化了jdbc的操作,小巧簡單實用。根據(jù)適合的應(yīng)用場景使用就好。

對于JDBC數(shù)據(jù)庫編程,一般有三方面的問題:

1、數(shù)據(jù)庫連接的管理
數(shù)據(jù)庫的連接作為寶貴的IO資源,通過池化的方式,犧牲空間換取時間,提高效率。
2、數(shù)據(jù)庫表與JavaBean之間的映射
就是數(shù)據(jù)庫中的表字段與JavaBean中的成員變量配對映射的問題。

這個問題可通過以下3種思路解決:

  • a. sql語句

    jdbc中執(zhí)行SQL時寫明表字段名以及對應(yīng)的參數(shù)即可。
    如,最簡單的對orm框架apache dbutils中的處理,這樣簡單粗暴,提供了更強的靈活性給用戶,對于一些復(fù)雜SQL,涉及多表操作,或統(tǒng)計信息之類的,很容易靈活自定義,但缺點就是開發(fā)工作量繁復(fù),也不適用于擴(kuò)展維護(hù)。

  • b. 在JavaBean中明確指定對應(yīng)表和對應(yīng)的字段

    如Hibernate的做法。這樣做的好處就是省事,不必每條數(shù)據(jù)庫操作都去寫SQL語句了。SQL語句已有Hibernae根據(jù)映射關(guān)系給你動態(tài)生成SQL去執(zhí)行。

  • c. 動態(tài)映射

    只指定一個表名稱即可,剩下的工具(如jfinal)幫你搞定,這么做實際上你的JavaBean的字段都不用自己寫。

    原理就在于,通過表名稱,可獲取到表結(jié)構(gòu)及表字段,剩下的就是填充構(gòu)造SQL了。那么,問題來了,屬性與字段如何對應(yīng)? 用時指定唄,用的時候指定即可。

    這同時就暴露了一個問題,表字段屬性與javaBean強相關(guān)了。如果表結(jié)構(gòu)有變動,就需要將所有有影響的地方修改。但好處是開發(fā)很迅速啊。

3、數(shù)據(jù)庫操作的監(jiān)控

通常,隨著系統(tǒng)運行,數(shù)據(jù)量提升后,會對數(shù)據(jù)庫方面開始優(yōu)化。優(yōu)化的前提需要依據(jù),依據(jù)對數(shù)據(jù)庫操作進(jìn)行的監(jiān)控記錄。

如, 提取熱點數(shù)據(jù)做緩存;執(zhí)行緩慢SQL做優(yōu)化;冗余數(shù)據(jù)優(yōu)化等等。


以上,僅是個人的學(xué)習(xí)總結(jié)分享,如有不到位甚至錯誤等方面,真誠歡迎各位賜教討論,互相學(xué)習(xí),謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,734評論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 4,012評論 0 11
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,578評論 19 139
  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,248評論 0 4
  • 一個戴著鋼絲邊眼鏡、衣服上盡是塵土的老人坐在路旁。河上搭著一座浮橋,大車、卡車、男人、女人和孩子們正涌過橋去。騾車...
    簡書茶館葉老板閱讀 2,335評論 6 32

友情鏈接更多精彩內(nèi)容