Apache dbutils源码分析

本文对apache dbutils项目的源码进行分析,目录如下:

1、基本使用

2、DBUtilsy源码分析

2.1 QueryRunner

2.2 AbstractQueryRunner

2.3 结果集处理

3、总结


1、基本使用

DBUtils 官方示例

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

DBUtils运行时序图
DBUtils运行时序图

上图中的第4步有误,就是返回而已

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

  1. 提供的多样的API及对SQL参数的封装处理
  2. 结果集转换到JavaBean的处理

接下来对源码进行分析

2、DBUtils源码分析

对DBUtils代码的分析按上述执行过程进行

2.1、QueryRunner

QueryRunner继承于AbstractQueryRunner类,提供了多样的增删改查接口,大多接口都是调用到最核心的几个方法上,本文仅取两个典型的方法:

  • <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的分析,在下面专门进行。

/**
 * 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对象进行初始化
        stmt = this.prepareStatement(conn, sql);
        
        // 将参数填充到语句中,该函数是在AbstractQueryRunner内实现的,下面做分析
        this.fillStatement(stmt, params);
        
        // 对结果集进行包装
        rs = this.wrap(stmt.executeQuery());
        // 对结果集进行转换处理
        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值
        // 设定为自增长id方式
        stmt = this.prepareStatement(conn, sql, Statement.RETURN_GENERATED_KEYS);
     
        // 遍历参数列表,逐条参数信息填充
        for (int i = 0; i < params.length; i++) {
            // 将参数填充到语句中,该函数是在AbstractQueryRunner内实现的,下面做分析
            this.fillStatement(stmt, params[i]);
            stmt.addBatch();
        }
        // 批量执行
        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执行类 AsyncQueryRunner,其内部最终也是通过QueryRunner来实现,此处就不做分析了。

2.2 AbstractQueryRunner

AbstractQueryRunner是一个抽象类,是 QueryRunner和 AsyncQueryRunner 的基类,主要提供了两方面的东西

  • 对SQL语句参数进行填充
  • 关闭数据库连接,SQL 参数的准备等

对参数填充的方法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值是合法参数
 * @throws SQLException
 *             if a database access error occurs
 */
 /**
  * fillStatement方法的处理流程:
  * fillStatement的主要作用就是将需要在SQL中添加的参数进行填充到准备语句中去。
  * 在填充过程中处理:
  *  1、通过参数个数做校验
  *     通过获取准备语句的参数元信息与参数值个数做比对,不合法则抛出SQL异常;
  *  2、调用准备语句方法(setObject(...))逐个参数值按序设定
  *  3、对参数值为null的处理
  *     参数值为null时,主要正确设置null对应参数的SQL类型。
  *     通过参数元信息中获取对应位置的参数类型信息,将此信息设置;否则就使用默认的vchar类型设定
  */
public void fillStatement(PreparedStatement stmt, Object... params)
        throws SQLException {

    // check the parameter count, if we can
    /* ParameterMetaData类用来表示PreparedStatement实例的参数相关信息,
     * 包括参数个数,类型等一些属性。这些属性信息通过 
     * ParameterMetaData getParameterMetaData() throws SQLException;  
     * 方法获取。ParameterMetaData的使用参考官方API文档即可。
     */
    ParameterMetaData pmd = null;
    
    /** pmdKnownBroken是本类的一个booleanl类型的成员变量,用来区分一个JDBC驱动是否支持
     *  {@link ParameterMetaData#getParameterType(int) };的操作。
     *  设置为true,则表示不支持;false 则表示支持
     */
    if (!pmdKnownBroken) {
        // 获取参数相关的元数据信息,包含参数个数和类型等信息
        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参数值的处理
            stmt.setNull(i + 1, sqlType);
        }
    }
}

DBUtils还提供了一种根据java实体和指定属性名的方式来进行参数填充

/**
 * Fill the <code>PreparedStatement</code> replacement parameters with the
 * given object's bean property values.
 * 通过bean以及要执行的属性名称来进行参数填充
 * 
 * @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];
    // 遍历传入的属性列表,匹配对应的实体属性,存储到sorted内
    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];
            // 此处直接进行比较                 
            if (propertyName.equals(descriptor.getName())) {
                /**
                 *  此处直接将元素属性名复制,作为表字段名称;如果要进行实体属性名(驼峰命名)
                 *  与数据库表字段名映射,可在下面代码中处理即可。
                 */
                sorted[i] = descriptor;
                found = true;
                break;
            }
        }
        if (!found) {
            throw new RuntimeException("Couldn't find bean property: "
                    + bean.getClass() + " " + propertyName);
        }
    }
    // 到此,只是将实体属性与给定的进行过滤提取了一遍,而真正的值还在实体bean内部
    fillStatementWithBean(stmt, bean, sorted);
}
   

上面调用了fillStatementWithBean(PreparedStatement stmt, Object bean,
PropertyDescriptor[] properties)来获取属性值并进行填充

/**
 * 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
 *             
 * 该方法将根据传入的实体属性集合通过反射bean获取属性值来进行参数填充
 */
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 {
            // 通过回调属性读方法获取到属性值
            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);
}

AbstractQueryRunner类还提供了一个抛出SQL、参数信息的异常方法:

/**
 * 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);

    // 添加出现异常的SQL和参数信息
    msg.append(" Query: ");
    msg.append(sql);
    msg.append(" Parameters: ");

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

    // 构建并抛出异常
    SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
            cause.getErrorCode());
    e.setNextException(cause);

    throw e;
}

2.3 结果集处理

ResultSetHandler接口,将ResultSet转换为一个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转换为一个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;

}

其实现有:

  • BeanHandler:将结果集中的第一行数据转换成一个JavaBean实例
  • BeanListHandler:将结果集中的每一行数据都转成一个JavaBean实例,存放到List中
  • BeanMapHandler:将结果集中的每一行数据都转成一个JavaBean实例,并指定某一列作为Key,存放到Map中
  • AbstractKeyedHandler:将每一行记录转换成一个键值对
  • AbstractListHandler:将结果集转换到一个List列表内
  • ArrayHandler:结果集中的第一行数据转换成Object数组
  • ArrayListHandler:把结果集中的每一行数据都转换成一个Object[]数组,再存放在List中
  • ColumnListHandler:将结果集中某一列的数据存放到List中

等等,提供了各种各样的实现。
此处,仅对BeanHandler类的实现做分析,其它大多雷同,就不分析了。

/**
 * <code>ResultSetHandler</code> implementation that converts the first
 * <code>ResultSet</code> row into a JavaBean. This class is thread safe.
 * 将结果集中的第一行数据转换成一个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.
     * 真正的转换器,基本所有的结果集的处理都在这个类里面,接下来做重点分析
     */
    private final RowProcessor convert;

    /**
     *  有两个构造方法省略......
     */

    /**
     * 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 {
        // 通过转换器来实现
        return rs.next() ? this.convert.toBean(rs, this.type) : null;
    }

}

很容易看出dbutils提供了一个对RowProcessor的基本实现:BasicRowProcessor
我们来看上面的转换器中的实现:

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

在BasicRowProcessor内又通过调用BeanProcessor的方法来实现,专门处理对JavaBean的转换
根据结果集和类类型来创建实体类,经历以下处理过程:

  1. 获取bean的所有类型
  2. 获取结果集中的类型和值
  3. 然后将结果集中的类型与Bean中的类型匹配(涉及到表字段的命名与实体属性名转换问题)
  4. 通过实体bean的setter方法反射设定
public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {

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

    // 获得结果集中的字段属性 
    // ResultSetMetaData为结果集元数据类,使用可参考官方API
    ResultSetMetaData rsmd = rs.getMetaData();

    //  columnToProperty[3]=4 就是说ResultSetMetaData里的第三个字段
    //   对应于bean的PropertyDescriptor里面的第四个属性
    // 数组中的数字是拿 结果集中的字段属性去按个匹配Bean中的属性,得到合法属性在结果集中的索引序列,
    // 后续转换bean时都以此为准,实际上就是将表中存在的字段在bean中不存在的过滤出去了,bean中不存在的
    // 是无法属性设定的
    int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

    // 创建实体对象
    return this.createBean(rs, type, props, columnToProperty);
}
// 将结果集中存在的属性在实体bean 中进行过滤,并返回存在的索引
// 为后续转换bean时提供依据,实际上就是将表中存在的字段在bean中不存在的过滤出去了,bean中不存在的
// 是无法属性设定的
protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
            PropertyDescriptor[] props) throws SQLException {

    int cols = rsmd.getColumnCount();
    
    // 定义并初始化数组为-1
    int[] columnToProperty = new int[cols + 1];
    Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

    // 逐个遍历结果集属性
    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;
}

已获得足够多的信息,开始创建实体Bean

/**
 * Creates a new object and initializes its fields from the ResultSet.
 * 创建实体类,并根据结果集初始化其属性值
 * @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 {

    // 创建对象
    T bean = this.newInstance(type);

    // 按提取好的合法的属性集逐个设定
    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) {
            
            // 从结果集中根据属性获得值
            value = this.processColumn(rs, i, propType);

            // 原生类型的值为空,则设定默认值
            if (value == null && propType.isPrimitive()) {
                // primitiveDefaults中通过静态块代码已将原生类型的默认值初始好了
                value = primitiveDefaults.get(propType);
            }
        }
        // 设定属性值
        this.callSetter(bean, prop, value);
    }

    return bean;
}

从结果集中根据属性获得值

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

    // index为结果集中的索引, proType为该索引对应的数据转换为的目的JavaBean内的属性
    // 根据索引和类型,将提取到的结果值做转换并返回
    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);
    }
}

设置属性值

// 调用反射调用JavaBean的setter方法,来注入结果集中的值
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
        // 对几种特殊类型做的转换处理
        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
        // 对参数类型和值类型做匹配检查
        if (this.isCompatibleType(value, params[0])) {
            
            // 调用set方法 设定值
            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());
    }
}

对参数的类型和值做匹配检查

// 对参数的类型和值做匹配检查
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的大多数的源码已经剖析完了,下面对此做个总结。

3、总结

dbutils对封装了jdbc的操作,简化了jdbc的操作,小巧简单实用。根据适合的应用场景使用就好。

对于JDBC数据库编程,一般有三方面的问题:

1、数据库连接的管理
数据库的连接作为宝贵的IO资源,通过池化的方式,牺牲空间换取时间,提高效率。
2、数据库表与JavaBean之间的映射
就是数据库中的表字段与JavaBean中的成员变量配对映射的问题。

这个问题可通过以下3种思路解决:

  • a. sql语句

    jdbc中执行SQL时写明表字段名以及对应的参数即可。
    如,最简单的对orm框架apache dbutils中的处理,这样简单粗暴,提供了更强的灵活性给用户,对于一些复杂SQL,涉及多表操作,或统计信息之类的,很容易灵活自定义,但缺点就是开发工作量繁复,也不适用于扩展维护。

  • b. 在JavaBean中明确指定对应表和对应的字段

    如Hibernate的做法。这样做的好处就是省事,不必每条数据库操作都去写SQL语句了。SQL语句已有Hibernae根据映射关系给你动态生成SQL去执行。

  • c. 动态映射

    只指定一个表名称即可,剩下的工具(如jfinal)帮你搞定,这么做实际上你的JavaBean的字段都不用自己写。

    原理就在于,通过表名称,可获取到表结构及表字段,剩下的就是填充构造SQL了。那么,问题来了,属性与字段如何对应? 用时指定呗,用的时候指定即可。

    这同时就暴露了一个问题,表字段属性与javaBean强相关了。如果表结构有变动,就需要将所有有影响的地方修改。但好处是开发很迅速啊。

3、数据库操作的监控

通常,随着系统运行,数据量提升后,会对数据库方面开始优化。优化的前提需要依据,依据对数据库操作进行的监控记录。

如, 提取热点数据做缓存;执行缓慢SQL做优化;冗余数据优化等等。


以上,仅是个人的学习总结分享,如有不到位甚至错误等方面,真诚欢迎各位赐教讨论,互相学习,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 一. Java基础部分.................................................
    wy_sure阅读 3,774评论 0 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,398评论 0 4
  • 一个戴着钢丝边眼镜、衣服上尽是尘土的老人坐在路旁。河上搭着一座浮桥,大车、卡车、男人、女人和孩子们正涌过桥去。骡车...
    简书茶馆叶老板阅读 2,225评论 6 32