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.
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
OrderItemrows as part of one aggregate save (see 07) shows up as oneINSERTline, not three — Spring Data JDBC callsaddBatch()three times andexecuteBatch()once. Counting SQL text seen byStatement.executeQuery/executeUpdatewould have reported 3 inserts; counting actualexecute*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.