Reputation: 622
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);
}
}
}
In the above code the ResultSet was opened by executeQuery
but is being closed by mapResultToRecord
. Is that the ideal place to close it or should the responsibility be taken up by the findRecords
function instead?
Upvotes: 3
Views: 499
Reputation: 96454
If you open it, close it. Whenever different code is responsible for closing a resource than is responsible for opening it, it's very hard to make sure the resource gets closed in all cases. If the same code opens and closes, it can use try-with-resources to make sure things get closed.
It would be an improvement for the executeQuery method to pass the mapper in as a callback. That is what Spring-jdbc does, it defines an interface, RowMapper, with this method:
T mapRow(ResultSet rs, int rowNum) throws SQLException
which is passed into query methods of the JdbcTemplate. The mapper is responsible solely for using a ResultSet row to populate an object and is not involved in closing anything. If the mapper's responsibility is limited to mapping a single row, and we can pass in the rownumber, then the same mapper can be used for looking up an individual row and for getting multiple rows.
Meanwhile the query-executing code becomes very general purpose, the query and the mapper are the only parts specific to a particular query, and they are both passed in from outside.
The findRecords method should execute the query and call the mapper to get the results from within a try-block that closes the resultSet, maybe like this:
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;
}
There isn't any detail given about what session.call does. It would probably be good for this code to be given the PreparedStatement as well so that it can make sure it gets closed.
Upvotes: 1
Reputation: 23139
My short answer is "When possible, Yes".
I think it makes more sense to close the ResultSet in findRecords
. It has to be understood that executeQuery
returns a resource that has to be closed. There's no way around that. Since it is findRecords
that calls executeQuery
, I think it's cleaner to have it be responsible for closing the ResultSet that comes back from doing so, in fitting with the idea behind your question.
A general rule is to have each function have a single purpose as much as possible. findRecords
's purpose is to utilize a query to retrieve and process some data. mapResultToRecord
's purpose is to create a list of records by pulling them from a ResultSet. This all reads very nicely:
public List<Record> findRecords(String name) {
String query = prepareQuery(name);
ResultSet resultSet = executeQuery(query);
List<Record> result = mapResultToRecord(resultSet);
resultSet.close();
return result;
}
Look at it the other way (the way you have it). findRecords
's purpose it to prepare and execute a query, then hand it off to mapResultsToRecord
for continued processing. mapResultsToRecord
's purpose is to create a list of records from the results of a query made elsewhere, and then to dispose of the results of the query.
Doesn't the first story read a lot better than the second? Aren't the concerns better separated and defined by the first one than the second? Just my humble $.02 worth.
To further stress my point, I'd say that if you were going to leave it the way it is, you'd want to rename mapResultsToRecord
to mapResultsToRecordAndCloseResultSet
.
UPDATE: @JohnKugelman's great idea provides yet another reason that in findRecords
is the place to close the ResultSet. Only in here does it make sense to use a try-with-resource block, to get this alternative:
public List<Record> findRecords(String name) throws SQLException {
String query = prepareQuery(name);
try (ResultSet resultSet = executeQuery(query)) {
return mapResultToRecord(resultSet);
}
}
This version is guaranteed to close the ResultSet, even if mapResultToRecord
throws an exception. Note the throws SQLException
on the method signature. This is necessary because, even though you don't see the <ResultSet>.close()
call, it's there implicitly, and it can throw a SQLException.
Upvotes: 2