Files
sdjpa4-demo/jdbc-vs-jpa/docs/06-when-this-gets-expensive.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.0 KiB

06 — When this gets expensive (production checklist)

← 05 the aggregate boundary

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 @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). 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 · README