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.
3.0 KiB
3.0 KiB
06 — When this gets expensive (production checklist)
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 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, 02, 04) 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
@EntityGraphannotations 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,
@BatchSizetuning) 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).
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
- p95 aggregate size (row count) for the collections you're considering moving.
- 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.
- Whether any code today relies on partial, incremental updates to a large collection
(
@Querybulk 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).