JDBC-封装

基本上所有在学习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最原始的方式,好吧,你再浪费你的生命。。。