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

49 lines
2.8 KiB
Markdown

# 03 — The SQL log
[← 02 the JPA side](02-the-jpa-side.md) · [next: the JDBC side →](04-the-jdbc-side.md)
Source: [`support/StatementLoggingDataSource.java`](../src/main/java/com/ankurm/jdbcvsjpa/support/StatementLoggingDataSource.java),
[`support/SqlLog.java`](../src/main/java/com/ankurm/jdbcvsjpa/support/SqlLog.java). Endpoint:
[`DiagController.java`](../src/main/java/com/ankurm/jdbcvsjpa/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.
```java
@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](output/07-delete-then-insert.txt)) 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](output/00-statement-count-comparison.txt)
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](04-the-jdbc-side.md).