关闭资源是否应该始终由打开资源的函数负责

  • 本文关键字:资源 函数 是否 java autocloseable
  • 更新时间 :
  • 英文 :

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职能部门承担责任?

我的简短回答是"如果可能,"是";。

我认为在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。这是必要的,因为即使您没有看到<ResultSet>.close()调用,它也隐式存在,并且可能引发SQLException。

如果你打开它,就关闭它。每当负责关闭资源的代码与负责打开资源的代码不同时,很难确保在所有情况下都关闭资源。如果同一个代码打开和关闭,它可以使用try-with-resources来确保关闭。

executeQuery方法将映射程序作为回调传入将是一种改进。这就是Springjdbc所做的,它用以下方法定义了一个接口RowMapper:

T mapRow(ResultSet rs, int rowNum) throws SQLException

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

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

findRecords方法应该执行查询并调用映射程序,以从关闭resultSet的try块中获取结果,可能如下所示:

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可能会很好,这样它就可以确保它被关闭。

最新更新