#640: JdbcExecutor.java

projectforge-business/src/main/java/org/projectforge/database/jdbc/JdbcExecutor.java Lines: 95 · Author: Kai Reinhard · Type: Java abstract class — Template Method pattern 95 lines · 61 code · 26 comments · 8 blank

Abstract JDBC operation executor using the Template Method pattern. 95 lines. This is the foundation for all raw JDBC operations in ProjectForge — DatabaseExecutorImpl creates anonymous subclasses for each SQL call. This class owns the connection lifecycle while letting subclasses define what happens with the statement.

Template Method — execute(String sql, boolean ignoreErrors, Object... args):

  1. Connection acquisition: dataSource.getConnection() — gets a raw JDBC connection from the pool (or directly in test mode)
  2. Statement preparation: con.prepareStatement(sql) — creates a PreparedStatement
  3. Parameter binding: iterates args[] and calls stmt.setObject(i+1, args[i]) — note the i+1 conversion: JDBC parameters are 1-based (JDBC spec), while Java arrays are 0-based. This is a common source of bugs if forgotten
  4. Delegation: calls execute(stmt) — the abstract method overridden by subclasses. The return value is typed as Object because subclasses return different types: null for DDL, List<DatabaseResultRow> for queries, Integer for updates/counts
  5. Error handling: if SQLException is caught and ignoreErrors=true, logs the error at ERROR level and returns null. If ignoreErrors=false, wraps in RuntimeException and rethrows — this is an unchecked exception design choice; callers don't need try-catch for SQL errors

Resource cleanup (finally block): Closes PreparedStatement first, then Connection. Each close is in its own try-catch — if closing the statement throws, the connection is still closed. Critical nuance: if either close throws an exception, a static boolean hasErrors flag is set. After both closes, if any errors occurred, a RuntimeException is thrown. This ensures resource leak errors are not silently swallowed — the caller knows resource cleanup failed.

Abstract method — execute(PreparedStatement stmt): Implemented by anonymous subclasses in DatabaseExecutorImpl for 4 different operations: DDL execution (stmt.execute()), query (stmt.executeQuery() + ResultSet mapping), scalar query (rs.getInt(1)), and update (stmt.executeUpdate()).

Missing connection pooling: The Javadoc warns "connections may loose" — this class gets a Connection via DataSource.getConnection() and closes it in finally. This is correct for pooled DataSources (the close returns the connection to the pool), but the author's concern was about direct DriverManager connections in test mode where each call opens a new TCP connection. In production with HikariCP, the pooling works fine — the warning is overly cautious.