Files
sdjpa4-demo/jdbc-vs-jpa/README.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

3.5 KiB

jdbc-vs-jpa

Companion code for Spring Data JDBC vs Spring Data JPA in 2026: When Dropping the ORM Is the Right Call on ankurm.com.

The same CustomerOrderOrderItem domain, modelled twice — once with Hibernate/Spring Data JPA (com.ankurm.jdbcvsjpa.jpa), once with Spring Data JDBC (com.ankurm.jdbcvsjpa.jdbc) — running against the same H2 database through the same StatementLoggingDataSource, so every SQL-statement count quoted in the article is a fair, apples-to-apples measurement, not two separate benchmarks run under different conditions.

Tested with: Spring Boot 4.1.1 / Spring Framework 7.0.9 / Spring Data JDBC 4.1.1 / Spring Data JPA (Hibernate ORM) via the Boot 4.1.1 BOM / H2 2.x / JDK 25 (Temurin 25.0.4.1).

Quickstart

./mvnw test                 # runs everything, regenerates docs/output/
./mvnw spring-boot:run       # then: curl localhost:8080/diag/sql-log

Where things are

Shared domain docs/01-the-shared-domain.md
JPA side docs/02-the-jpa-side.mdsrc/main/java/.../jpa/
The SQL log mechanism docs/03-the-sql-log.mdsrc/main/java/.../support/
JDBC side docs/04-the-jdbc-side.mdsrc/main/java/.../jdbc/
The aggregate boundary docs/05-the-aggregate-boundary.md
Production checklist docs/06-when-this-gets-expensive.md

Captured output

Every number quoted in the article is one of these files, regenerated by ./mvnw test:

File What it shows
docs/output/00-statement-count-comparison.txt The headline table: statement counts, JPA vs JDBC, across order/item counts
docs/output/01-lazy-outside-session.txt LazyInitializationException triggered for real
docs/output/02-n-plus-one.txt N+1 from a plain findAll() + loop
docs/output/03-entity-graph-fixed-cost.txt @EntityGraph flattening the cost to a constant
docs/output/04-dirty-checking-autoflush.txt An UPDATE with no save() call anywhere
docs/output/05-orphan-removal-delete.txt orphanRemoval turning a list removal into a DELETE
docs/output/06-fixed-cost-load.txt Spring Data JDBC's fixed-statement-count findById
docs/output/07-delete-then-insert.txt The delete-then-batched-insert collection replace
docs/output/08-aggregate-reference-no-join.txt AggregateReference costing zero extra statements
docs/output/09-optimistic-locking.txt @Version conflict on both stacks
docs/output/10-list-order-key-column.txt keyColumn making list order deterministic

Diagnostic endpoint

GET /diag/sql-log dumps every statement executed in this JVM since the last reset — the same mechanism the tests use. Delete DiagController before shipping; it is a demo aid, not a feature.