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.
54 lines
3.0 KiB
Markdown
54 lines
3.0 KiB
Markdown
# 06 — When this gets expensive (production checklist)
|
|
|
|
[← 05 the aggregate boundary](05-the-aggregate-boundary.md)
|
|
|
|
A short, honest checklist for deciding between the two stacks on a real project, expanding on the
|
|
article's closing "should you even do this" callout.
|
|
|
|
## Reach for Spring Data JDBC when
|
|
|
|
- Your aggregates are genuinely small (single digits to low tens of child rows). The
|
|
delete-and-reinsert cost in [07](output/07-delete-then-insert.txt) is proportional to
|
|
aggregate size on every save, not to what changed.
|
|
- You have been bitten by `LazyInitializationException`, N+1 queries you didn't predict, or a
|
|
flush timing surprise (scenarios [01](output/01-lazy-outside-session.txt),
|
|
[02](output/02-n-plus-one.txt), [04](output/04-dirty-checking-autoflush.txt)) more than once on
|
|
the same codebase.
|
|
- You want the query plan for "load this thing" to be readable from the entity class alone, with
|
|
no session state, no `@EntityGraph` annotations to remember, and no proxy that decides at
|
|
runtime whether to hit the database.
|
|
- You are comfortable writing more explicit queries (`@Query`, `JdbcAggregateTemplate`) for
|
|
anything that crosses an aggregate boundary, because that boundary is enforced rather than
|
|
advisory.
|
|
|
|
## Stay on Spring Data JPA when
|
|
|
|
- Collections are large or unbounded (hundreds to thousands of child rows) and updated
|
|
incrementally — the full delete-and-reinsert becomes real, measurable write amplification.
|
|
- Your domain genuinely benefits from a persistence-context-managed object graph: complex
|
|
bidirectional relationships, inheritance hierarchies, or second-level caching.
|
|
- Your team already has deep Hibernate operational experience (query plan reading, N+1 detection
|
|
tooling, `@BatchSize` tuning) and the tooling investment already exists.
|
|
- You need JPQL/Criteria API's richer query composition, or a library that specifically expects
|
|
`EntityManager`.
|
|
|
|
## What migrating does NOT require
|
|
|
|
You do not have to choose one stack for a whole application. This module runs both in one Spring
|
|
context (`@EnableJpaRepositories` scoped to one package, `@EnableJdbcRepositories` scoped to
|
|
another — see [`JdbcVsJpaApplication.java`](../src/main/java/com/ankurm/jdbcvsjpa/JdbcVsJpaApplication.java)).
|
|
A pragmatic migration moves the aggregates that are small, hot, and bug-prone under JPA's
|
|
lazy-loading surprises first, and leaves the genuinely graph-shaped, cache-heavy parts of the
|
|
domain on JPA.
|
|
|
|
## What to measure before deciding, on your own schema
|
|
|
|
1. p95 aggregate size (row count) for the collections you're considering moving.
|
|
2. Write frequency versus read frequency for those aggregates — JDBC's replace-on-save cost is a
|
|
write-path cost; if reads dominate, it may not matter.
|
|
3. Whether any code today relies on partial, incremental updates to a large collection
|
|
(`@Query` bulk updates exist on both sides, but the ORM-managed dirty-checking path some teams
|
|
lean on for this does not exist under Spring Data JDBC at all).
|
|
|
|
[← back to the shared domain](01-the-shared-domain.md) · [README](../README.md)
|