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.
This commit is contained in:
2026-09-17 19:09:35 +00:00
parent 585eed4d57
commit 448c383879
41 changed files with 1811 additions and 17 deletions
+56
View File
@@ -0,0 +1,56 @@
# jdbc-vs-jpa
Companion code for **[Spring Data JDBC vs Spring Data JPA in 2026: When Dropping the ORM Is the
Right Call](https://ankurm.com/)** on [ankurm.com](https://ankurm.com).
The same `Customer``Order``OrderItem` 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`](src/main/java/com/ankurm/jdbcvsjpa/support/StatementLoggingDataSource.java),
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
```bash
./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](docs/01-the-shared-domain.md) |
| JPA side | [docs/02-the-jpa-side.md](docs/02-the-jpa-side.md) — `src/main/java/.../jpa/` |
| The SQL log mechanism | [docs/03-the-sql-log.md](docs/03-the-sql-log.md) — `src/main/java/.../support/` |
| JDBC side | [docs/04-the-jdbc-side.md](docs/04-the-jdbc-side.md) — `src/main/java/.../jdbc/` |
| The aggregate boundary | [docs/05-the-aggregate-boundary.md](docs/05-the-aggregate-boundary.md) |
| Production checklist | [docs/06-when-this-gets-expensive.md](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](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](docs/output/01-lazy-outside-session.txt) | `LazyInitializationException` triggered for real |
| [docs/output/02-n-plus-one.txt](docs/output/02-n-plus-one.txt) | N+1 from a plain `findAll()` + loop |
| [docs/output/03-entity-graph-fixed-cost.txt](docs/output/03-entity-graph-fixed-cost.txt) | `@EntityGraph` flattening the cost to a constant |
| [docs/output/04-dirty-checking-autoflush.txt](docs/output/04-dirty-checking-autoflush.txt) | An UPDATE with no `save()` call anywhere |
| [docs/output/05-orphan-removal-delete.txt](docs/output/05-orphan-removal-delete.txt) | `orphanRemoval` turning a list removal into a DELETE |
| [docs/output/06-fixed-cost-load.txt](docs/output/06-fixed-cost-load.txt) | Spring Data JDBC's fixed-statement-count `findById` |
| [docs/output/07-delete-then-insert.txt](docs/output/07-delete-then-insert.txt) | The delete-then-batched-insert collection replace |
| [docs/output/08-aggregate-reference-no-join.txt](docs/output/08-aggregate-reference-no-join.txt) | `AggregateReference` costing zero extra statements |
| [docs/output/09-optimistic-locking.txt](docs/output/09-optimistic-locking.txt) | `@Version` conflict on both stacks |
| [docs/output/10-list-order-key-column.txt](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.