基本上所有在学习jdbc的时候,都做过简单的封装,jdbc的基本流程如下

  1. 定义数据库连接参数
  2. 打开数据库连接
  3. 声明SQL语句
  4. 预编译并执行SQL语句
  5. 遍历查询结果(如果需要的话)
  6. 处理每一次遍历操作
  7. 处理抛出的任何异常
  8. 处理事务
  9. 关闭数据库连接

    其中第一和第二步已经由我们的数据源管理处理了。但是接下来的N步,每次都要写也是繁琐的。于是首先我们可以把好抽象的部分拿出来。代码如下:

    public static int execute(String sql, Object... params) {
        Connection connection = null;
        PreparedStatement ps = null;
        try {
            connection = getConnection();
            ps = connection.prepareStatement(sql);
            fillStatement(ps, params);
            return ps.executeUpdate();
        } catch (Exception e) {
            logger.error("execute sql error ! sql:{} params:{}", sql, params, e);
            throw new DBException(e);
        } finally {
            dispose(null, ps, connection);
        }
    }

可以看到,我们这个方法需要传入sql和参数,之后获取PreparedStatement,参数的设置使用占位符的方式,这里有一个技巧就是在setParam的时候全部使用setObject方法,这样可以避免掉类型的转换处理,fillStatement代码如下:

    public static void fillStatement(PreparedStatement stmt, Object... params)
            throws SQLException {
        if (params == null) {
            return;
        }
        ParameterMetaData pmd = null;
        if (!pmdKnownBroken) {
            try {
                pmd = stmt.getParameterMetaData();
                if (pmd.getParameterCount() < params.length) {
                    throw new SQLException("Too many parameters: expected "
                            + pmd.getParameterCount() + ", was given "
                            + params.length);
                }
            } catch (Exception e) {
                pmdKnownBroken = true;
            }
        }
        for (int i = 0; i < params.length; i++) {
            if (params[i] != null) {
                try {
                    stmt.setObject(i + 1, params[i]);
                } catch (Exception e) {
                    // 处理oracle的日期类型,如果java.util.Date设置失败,使用java.sql.Timestamp
                    if (params[i] instanceof java.util.Date) {
                        Timestamp date = new Timestamp(
                                ((java.util.Date) params[i]).getTime());
                        stmt.setObject(i + 1, date);
                    }
                }
            } 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 {
                        sqlType = pmd.getParameterType(i + 1);
                    } catch (SQLException e) {
                        pmdKnownBroken = true;
                    }
                }
                stmt.setNull(i + 1, sqlType);
            }
        }
    }

在注释部分处理了Oracle数据库的日期设置,避免了set失败的问题
这样一个执行sql的方法就可以用了,用这个方法可以执行update,delete但是insert方法可能由一点差别,具体见:JDBC-方言,但是最复杂多变的在于select,返回多种多样的数据,需要我们处理成不同的格式。JDBC执行完查询时候,都会返回一个ResultSet,对于rs的处理, 可以分为几类

1.返回单表对象:一个或者多
2.count等函数查询,返回数值或者单个字段的
3.返回多个表关联对象:一个或者多个

我们需要针对不同的返回结果进行处理,这里要提到一个轻量级的工具DBUtils,仅仅几十个类,完成了这些抽象,设计的非常巧妙,上面的fillStatement方法就是从DBUtils中借鉴的
针对不同的返回结果,DBUtils定义了一个接口ResultSetHandler,如下:

public interface ResultSetHandler<T> {
    /**
     * Turn the <code>ResultSet</code> into an 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;
}

这个接口只有一个方法,就是处理rs,返回的结果是一个泛型的结果,然后又定义了各种的抽象类及具体的实现,如
dbutils handler类图
在接口下提供了很多的实现,包括了我们上面提到的所有返回结果的处理,在使用的时候只要使用对应的处理实现即可。这里不再赘述,具体请自行查看

dbutils的使用。使用了DBUtils之后,我们的封装变成了如下结果:

    public static <T> List<T> query(Class<T> beanClass, String sql,
            Object... params) {
        Connection conn = getConnection();
        try {
            return queryRunner.query(conn, sql,
                    Reflect.isPrimitive(beanClass) ? new ColumnListHandler<T>()
                            : new BeanListHandler<T>(beanClass,
                                    new CustomRowProcessor(
                                            new StrategyBeanProcessor(
                                                    new HumpMatcher()))),
                    params);
        } catch (SQLException e) {
            logger.error("Query list error !", e);
            throw new DBException(e);
        } finally {
            freeConnection(conn);
        }
    }

query方法只需要传入特定的class,sql和参数,即可把返回结果处理为class对应的对象,在也不用编写繁琐的rs.getxxx, bean.setxxx。实现原理就是通过获取传入class的类描述及字段描述等信息,然后调用具体的set方法给字段赋值返回即可,相应的返回map等实现都大同小异。

如果你想使用jdbc,使用dbutils再加上自己的一点封装即可, 如果你还在使用jdbc最原始的方式,好吧,你再浪费你的生命。。。

Comments
Write a Comment