【问题标题】:Should closing a resource always be a responsibility of the function that opens it?关闭资源是否应该始终由打开它的功能负责?
【发布时间】:2021-02-23 02:25:15
【问题描述】:
public ResultSet executeQuery(String query) {
    return session.call(query)
}


public List<Record> findRecords(String name) {
    String query = prepareQuery(name);
    return mapResultToRecord(executeQuery(query));
}

public List<Record> mapResultToRecord(ResultSet rs) {
    try {
        List<Record> list = new ArrayList<>();
        while(rs.hasNext()) {
            list.add(new Record(rs.next()));
        }
    } catch (Exception e) {
        logger.warn("exception while iterating over the recall set", e);
    } finally {
        try {
            rs.close();
        } catch (Exception ex) {
            logger.warn("exception while closing the result set", ex);
        }
    }
}

在上面的代码中,ResultSet 由executeQuery 打开,但由mapResultToRecord 关闭。这是关闭它的理想位置还是应该由findRecords 函数承担责任?

【问题讨论】:

  • 这无论如何都行不通。要么您正在泄漏Connection,要么您正在关闭它,这将关闭ResultSet,或者您正在尝试使用持久连接,这很容易失败:您应该使用本地Connection,本地PreparedStatement 和本地的ResultSet,然后将它们全部关闭,并用连接池支持Connection

标签: java autocloseable


【解决方案1】:

我的简短回答是“如果可能的话,是的”。

我认为关闭findRecords 中的ResultSet 更有意义。必须理解executeQuery 返回一个必须关闭的资源。没有办法解决这个问题。由于是 findRecords 调用了 executeQuery,我认为让它负责关闭这样做回来的 ResultSet 会更干净,这符合您的问题背后的想法。

一般规则是尽可能让每个功能都有一个目的。 findRecords 的目的是利用查询来检索和处理一些数据。 mapResultToRecord 的目的是通过从 ResultSet 中提取记录来创建记录列表。这一切读起来都很好:

public List<Record> findRecords(String name) {
    String query = prepareQuery(name);
    ResultSet resultSet = executeQuery(query);
    List<Record> result = mapResultToRecord(resultSet);
    resultSet.close();
    return result;
}

换个角度看(你拥有它的方式)。 findRecords 的目的是准备和执行查询,然后将其交给 mapResultsToRecord 以继续处理。 mapResultsToRecord 的目的是根据在别处进行的查询结果创建记录列表,然后处理查询结果。

第一个故事不是比第二个好很多吗?第一个问题不是比第二个问题更好地分离和定义问题吗?只是我不起眼的 0.02 美元。

为了进一步强调我的观点,我想说,如果您要保持原样,您需要将 mapResultsToRecord 重命名为 mapResultsToRecordAndCloseResultSet

更新:@JohnKugelman 的好主意提供了另一个原因,即在findRecords 中是关闭 ResultSet 的地方。只有在这里才有意义使用 try-with-resource 块来获得这种替代方案:

public List<Record> findRecords(String name) throws SQLException {
    String query = prepareQuery(name);
    try (ResultSet resultSet = executeQuery(query)) {
        return mapResultToRecord(resultSet);
    }
}

此版本保证关闭 ResultSet,即使mapResultToRecord 抛出异常。注意方法签名上的throws SQLException。这是必要的,因为即使您没有看到 &lt;ResultSet&gt;.close() 调用,它也隐含地存在,并且它可以抛出 SQLException。

【讨论】:

    【解决方案2】:

    如果您打开它,请关闭它。每当不同的代码负责关闭资源而不是负责打开它时,很难确保资源在所有情况下都被关闭。如果打开和关闭相同的代码,它可以使用 try-with-resources 来确保关闭。

    executeQuery 方法将映射器作为回调传递是一种改进。这就是 Spring-jdbc 所做的,它用这个方法定义了一个接口,RowMapper

    T mapRow(ResultSet rs, int rowNum) throws SQLException
    

    传入 JdbcTemplate 的查询方法中。映射器仅负责使用 ResultSet 行来填充对象,并且不参与关闭任何内容。如果映射器的职责仅限于映射单行,并且我们可以传入行号,那么同一个映射器可以用于查找单个行和获取多行。

    同时查询执行代码变得非常通用,查询和映射器是特定查询的唯一部分,它们都是从外部传入的。

    findRecords 方法应该执行查询并调用映射器以从关闭 resultSet 的 try-block 中获取结果,可能是这样的:

    List<T> findRows(String queryName, RowMapper<T> mapper) throws SQLException {
        List<T> list = new ArrayList<>();
        String sql = prepareQuery(queryName);
        try (ResultSet resultSet = session.call(sql);) {
            for (int i = 1; resultSet.hasNext(); i += 1) {
                list.add(mapper.mapRow(resultSet, i));
            }
        }
        return list;
    }
    

    没有详细说明 session.call 的作用。给这段代码也提供 PreparedStatement 可能会很好,这样它就可以确保它被关闭。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多