Files
sdjpa4-demo/jdbc-vs-jpa/docs/03-the-sql-log.md
T
asmhatre 448c383879 Add jdbc-vs-jpa module: Spring Data JDBC vs JPA on a shared Order aggregate
Same Customer/Order/OrderItem domain modelled with Hibernate/Spring Data JPA and
Spring Data JDBC side by side, both running through a shared StatementLoggingDataSource
so SQL-statement counts are directly comparable. 11 tests, 11 captured transcripts,
6 doc chapters. Companion repo for the ankurm.com article on when to drop the ORM.
2026-09-17 19:09:35 +00:00

2.8 KiB

03 — The SQL log

← 02 the JPA side · next: the JDBC side →

Source: support/StatementLoggingDataSource.java, support/SqlLog.java. Endpoint: DiagController.java at /diag/sql-log.

Every statement-count claim in this article comes from one mechanism, not from reading Hibernate's show_sql output in one format and Spring Data JDBC's JdbcTemplate logging in a different one. StatementLoggingDataSource wraps the single H2 DataSource both stacks share: it hands out a JDK dynamic proxy for every Connection, which in turn hands out a proxy for every Statement/PreparedStatement, and any method starting with execute gets its SQL text recorded to SqlLog before the call is delegated. This is deliberately below both ORMs — it counts what actually reached the database driver, not what each framework's own debug logging chose to print.

@Bean
public DataSource dataSource(SqlLog sqlLog) {
    HikariDataSource real = new HikariDataSource();
    real.setJdbcUrl("jdbc:h2:mem:jdbcvsjpa;DB_CLOSE_DELAY=-1;MODE=LEGACY");
    // ...
    return new StatementLoggingDataSource(real, sqlLog);
}

Two things this caught that a naive count would have missed:

  • JDBC batching. Saving three OrderItem rows as part of one aggregate save (see 07) shows up as one INSERT line, not three — Spring Data JDBC calls addBatch() three times and executeBatch() once. Counting SQL text seen by Statement.executeQuery/executeUpdate would have reported 3 inserts; counting actual execute* invocations on the proxy correctly reports 1, because that is the true number of round trips to the database.
  • Consistent counting across two completely different SQL-generation paths. Hibernate's HQL compiler and Spring Data JDBC's JdbcTemplate-based query building produce differently formatted SQL for the same logical operation (see the JPA transcripts' lower-case, unquoted style versus the JDBC transcripts' upper-case, quoted style — both are the frameworks' own defaults, untouched). A statement counter that lived inside either framework would only ever see its own side; this one sees both, so 00 — the comparison table is a fair, apples-to-apples number.

/diag/sql-log exposes the same log at runtime for manual exploration — hit an endpoint, then curl localhost:8080/diag/sql-log to see exactly what ran. Delete this controller before shipping; it has no business existing outside a demo.

Continue to 04 — the JDBC side.