# 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)