Summary
DbInteractions.query(...) returns a ResultSet (a disconnected CachedRowSet as of #143). Because the raw row cursor is handed back to callers, every repository re-implements the same iterate-map-and-error-handle boilerplate. A RowMapper-style API that returns List<T> would move that loop (and all SQLException/null handling) into one place and let repositories return mapped objects directly.
This is a follow-up to #143, which fixed the resource leak (#140) and parameterized the SQL (#139) with minimal blast radius but deliberately left the ResultSet-returning shape in place.
The repeated boilerplate
Each *RepositoryImpl query method follows the same pattern — e.g. src/main/java/preponderous/viron/repositories/EnvironmentRepositoryImpl.java:26-37:
List<Environment> environments = new ArrayList<>();
ResultSet rs = dbInteractions.query("SELECT * FROM viron.environment");
if (rs == null) {
log.error("Error finding all environments: ResultSet is null");
return environments;
}
try {
while (rs.next()) {
environments.add(mapResultSetToEnvironment(rs));
}
} catch (SQLException e) {
log.error(...);
}
return environments;
This null-guard + try/while (rs.next()) + catch (SQLException) block is repeated across ~18 query methods in the four repositories (null-guards: EnvironmentRepositoryImpl 7, EntityRepositoryImpl 6, GridRepositoryImpl 4, LocationRepositoryImpl 1), and each repository carries a near-identical mapResultSetTo* row-mapper helper:
EnvironmentRepositoryImpl.java:195 mapResultSetToEnvironment
EntityRepositoryImpl.java:23 mapResultSetToEntity
GridRepositoryImpl.java:23 mapResultSetToGrid
LocationRepositoryImpl.java:23 mapResultSetToLocation
Why it matters
Suggested approach
- Add a functional interface:
@FunctionalInterface
public interface RowMapper<T> {
T map(ResultSet rs) throws SQLException;
}
- Add
DbInteractions.query overloads that own the cursor and return mapped results, e.g.:
<T> List<T> query(String sql, RowMapper<T> mapper, Object... params); // 0..n rows
<T> Optional<T> queryOne(String sql, RowMapper<T> mapper, Object... params); // 0..1 row
These iterate, map, and close the PreparedStatement/ResultSet internally, returning an empty List/Optional on error.
- Migrate each repository method to pass its existing
mapResultSetTo* helper (or a method reference) and return the result directly, deleting the per-method null-guard and try/catch boilerplate.
- Once all callers are migrated, remove the
ResultSet-returning query (and the CachedRowSet machinery it needs).
This is a larger, mechanical refactor touching all four repositories and the factories, so it is intentionally separated from #143. It pairs naturally with #130 (data-layer/contract hygiene).
Filed by Claude during an automated triage pass; claims above were verified against source.
Summary
DbInteractions.query(...)returns aResultSet(a disconnectedCachedRowSetas of #143). Because the raw row cursor is handed back to callers, every repository re-implements the same iterate-map-and-error-handle boilerplate. ARowMapper-style API that returnsList<T>would move that loop (and allSQLException/null handling) into one place and let repositories return mapped objects directly.This is a follow-up to #143, which fixed the resource leak (#140) and parameterized the SQL (#139) with minimal blast radius but deliberately left the
ResultSet-returning shape in place.The repeated boilerplate
Each
*RepositoryImplquery method follows the same pattern — e.g.src/main/java/preponderous/viron/repositories/EnvironmentRepositoryImpl.java:26-37:This
null-guard +try/while (rs.next())+catch (SQLException)block is repeated across ~18 query methods in the four repositories (null-guards:EnvironmentRepositoryImpl7,EntityRepositoryImpl6,GridRepositoryImpl4,LocationRepositoryImpl1), and each repository carries a near-identicalmapResultSetTo*row-mapper helper:EnvironmentRepositoryImpl.java:195mapResultSetToEnvironmentEntityRepositoryImpl.java:23mapResultSetToEntityGridRepositoryImpl.java:23mapResultSetToGridLocationRepositoryImpl.java:23mapResultSetToLocationWhy it matters
nullcheck, and theSQLExceptionswallow all currently live in every repository method.if (rs == null)guard (fixed in EnvironmentRepositoryImpl lacks null-ResultSet guard present in sibling repositories #132 for the repositories and EntityFactory is missing the null-ResultSet guard and id==-1 check that EnvironmentFactory has #138 forEntityFactory). If callers never touch aResultSet, they cannot forget to guard it.ResultSetownership insideDbInteractions, where it can be opened and closed in one place (reinforcing the DbInteractions.query/update never close the Statement (JDBC resource leak) #140 fix), rather than handing a cursor to callers.Suggested approach
DbInteractions.queryoverloads that own the cursor and return mapped results, e.g.:PreparedStatement/ResultSetinternally, returning an emptyList/Optionalon error.mapResultSetTo*helper (or a method reference) and return the result directly, deleting the per-methodnull-guard andtry/catchboilerplate.ResultSet-returningquery(and theCachedRowSetmachinery it needs).This is a larger, mechanical refactor touching all four repositories and the factories, so it is intentionally separated from #143. It pairs naturally with #130 (data-layer/contract hygiene).
Filed by Claude during an automated triage pass; claims above were verified against source.