在关闭ResultSet之前需要从ResultSet中填充时,如何抽象出JDBC代码?

时间:2019-01-10 20:30:42

标签: java jdbc

我正在开发一个Java应用程序,该应用程序对数据库执行许多JDBC查询,每个查询都是在单独的方法中指定的,大致采用以下格式。

public static void sampleJDBCQuery(String query, DBUtil dbUtil, DataStructure dataStructure) {
    ResultSet rs = null;
    Handle handle = null;
    Connection conn = null;
    Statement stmt = null;
    LOGGER.debug("Executing query = {}", query);
    try {
        handle = dbUtil.getConnectionHandle();
        conn = handle.getConnection();
        if (conn != null) {
            stmt = conn.createStatement();
            if (stmt != null) {
                rs = stmt.executeQuery(query);
                if (rs != null) {
                    while (rs.next()) {
                        // populate dataStructure using rs
                    }
                }
            }
        }
    } catch (Exception e) {
        Metrics.markMeter("vertica.read.error");
        LOGGER.error(e.getMessage(), e);
    } finally {
        DBUtil.closeResultSet(rs);
        DBUtil.closeStatement(stmt);
        DBUtil.close(handle);
        LOGGER.debug("Finished query = {}", query);
    }
}

有了上述格式的许多不同的示例查询(和数据结构类),我的代码库已经有了很大的增长。我的目标是拥有一个帮助程序方法,可以为我抽象出大部分JDBC逻辑。我的第一个想法是要有一个带有以下签名的方法。

public static ResultSet executeJDBCQuery(String query, DBUtil dbUtil)

然后我可以遍历ResultSet的行,并为每一行填充相关的DataStructure。问题是我仍然必须关闭返回的ResultSet并以另一种方法关闭ResultSet似乎是不好的设计。

我想我所寻找的可能与Python的函数装饰器概念类似,因此我可以“修饰” JDBC查询以处理上面sampleJDBCQuery中存在的大多数样板。我该如何实现?

1 个答案:

答案 0 :(得分:2)

您可以传入一种策略来告诉该方法如何将行映射到对象上的字段。

这是spring-jdbc所做的,它将RowMapper定义为:

public interface RowMapper<T> {
    T mapRow(ResultSet resultSet, int rowNum) throws SQLException;
}

这是更改方法的方法,包括合并rowMapper:

public static <T> List<T> queryList(String query, Connection conn, RowMapper<T> rowMapper) throws SQLException {
    LOGGER.debug("Executing query = {}", query);
    try {
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        List<> list = new ArrayList<>();    
        while (rs.next()) {
            list.add(rowMapper.mapRow(rs, list.size() + 1)); 
        }
        return list;
    } finally {
        DBUtil.closeResultSet(rs);
        DBUtil.closeStatement(stmt);
        LOGGER.debug("Finished query = {}", query);
    }
}

在此处捕获所有异常不是一件好事,因为如果发生问题,您希望能够使用异常来退出当前操作。否则,您将在日志中有多个错误堆栈跟踪,一个在此处发生错误,然后在另一个下游,您的代码在期望出现并非由先前错误引起的结果时失败。发生第一个错误时,只需快速失败即可。

下一步将是对查询进行参数化,因此您不必在引号中包含参数值或担心sql注入。有关spring-jdbc如何处理此问题的示例,请参见this answer

我将连接内容从方法中移出了;传递连接允许您在同一JDBC本地事务中执行多个sql语句。 (连接仍然需要关闭,这只是做错了地方。)

在此传递此句柄也违反了the Law of Demeter

  

尤其是,对象应避免调用另一个方法返回的成员对象的方法。对于许多使用点作为字段标识符的现代面向对象语言,可以将法律简单地表述为“仅使用一个点”。也就是说,代码a.b.Method()违反了法律,而a.Method()没有。打个比方,当一个人要一只狗走路时,并不命令狗的腿直接走路;取而代之的是命令狗,然后命令自己的腿。

即使您只做只读工作,让查询共享事务也可以提高一致性并提高性能。在Spring中,使用事务的痛苦较小,您可以使用批注来声明性地实现事务,以显示边界的位置。

这里的全局答案是:最好采用一个预先存在的工具(spring-jdbc或类似的东西),在该工具中,他们已经解决了您甚至还没有考虑的问题,而不是重塑它,这显然是您要走的路。