Spring Data JDBC vs. Spring Data JPA
Spring Data JPA isn't the only Spring Data persistence option — Spring Data JDBC deliberately removes the persistence context entirely, trading JPA's automatic convenience for a smaller, more predictable, fully explicit model.
Learning objectives
- Explain what Spring Data JDBC removes (persistence context, dirty checking, lazy loading) compared to JPA.
- Choose the right tool for a given domain model's complexity.
- Explain why a mutated Spring Data JDBC entity requires an explicit save() call, unlike a managed JPA entity.
Spring Data JPA isn't the only Spring Data persistence option — knowing exactly what JPA's automatic behaviors cost you is what lets you recognize when a simpler tool actually fits better.
📖 Story
Imagine two ways to manage a household budget. One hires a full-time accountant who watches every transaction continuously and automatically reconciles everything — powerful, but you're paying for that constant watching. The other is simpler: you write down a transaction when it happens, nothing more.
Here's what "nothing more" looks like in Spring Data JDBC — notice what's MISSING compared to JPA:
public interface CustomerRepository extends CrudRepository<Customer, Long> { } Customer customer = customerRepository.findById(1L).get(); customer.setName("New Name"); // customer.setName() alone does NOTHING here — unlike JPA's dirty checking // (Chapter 4), there's no persistence context watching this object at all. customerRepository.save(customer); // you MUST call this explicitly, every time.
No persistence context. No dirty checking. No lazy loading — every association loads fully, every time. This isn't a limitation to work around; it's the entire design.
Spring Data JDBC — a Spring Data module providing repository abstractions built directly on JDBC, with NO persistence context, dirty checking, or lazy loading. Aggregate — Spring Data JDBC's core modeling concept: a cluster of entities loaded and saved together, with no partial loading.
Let's compare this chapter's Customer example side by side with the JPA equivalent you already know.
// JPA — dirty checking saves automatically: @Transactional public void updateName(Long id, String name) { Customer c = entityManager.find(Customer.class, id); c.setName(name); // no save() call needed — Chapter 4's dirty checking handles it } // Spring Data JDBC — explicit, every single time: public void updateName(Long id, String name) { Customer c = customerRepository.findById(id).orElseThrow(); c.setName(name); customerRepository.save(c); // REQUIRED — nothing happens without this }
Neither version is "wrong" — they're different philosophies. JPA's implicit behaviors (this course's entire N+1 chapter, the proxy trap from Chapter 5, the "mystery UPDATE" from Chapter 4) are ALL sources of real surprise specifically because they're implicit. Spring Data JDBC trades away the convenience specifically to eliminate that whole category of surprise — at the cost of writing more explicit code, like the save() call above.
Spring Data JDBC's repository implementation, at each save() call, directly issues the necessary INSERT/UPDATE immediately — no persistence context tracking anything in the background. Loading an aggregate always constructs the FULL object graph via explicit joins every single time, with no proxy objects and no possibility of LazyInitializationException, since nothing is ever lazily loaded in the first place.
- A microservice with a genuinely simple domain model (shallow relationships, straightforward CRUD) is a strong real fit for Spring Data JDBC, trading JPA's power for a smaller, predictable surface area.
- A team repeatedly burned by
LazyInitializationExceptionand N+1 surprises sometimes migrates specific, simpler bounded contexts to Spring Data JDBC to eliminate that entire bug category by construction.
- Choose Spring Data JDBC deliberately for genuinely simple aggregates where JPA's automatic behaviors add more overhead than value.
- Understand "simpler" means MORE explicit code — this chapter's
save()call, always required — not less code overall. - Don't mix the two persistence models within the same bounded context without a clear reason.
⚠️ Why this keeps happening
Both tools share the "Spring Data Repository" abstraction, so it's easy to assume they behave the same way underneath.
- Expecting a mutated Spring Data JDBC entity to auto-save, exactly this chapter's opening trap — it won't, ever.
- Assuming Spring Data JDBC supports lazy loading with some configuration flag — it structurally doesn't.
- Choosing Spring Data JDBC for a genuinely complex, deeply-relational domain, then manually reimplementing ad-hoc dirty checking and lazy loading yourself.
Spring Data JDBC can be genuinely faster for simple, predictable access patterns, specifically because there's no persistence-context bookkeeping overhead. For complex object graphs, JPA's fetch-strategy tools (Chapter 12) can outperform Spring Data JDBC's always-eager-and-full loading.
No security-specific difference — both rely on the same underlying JDBC parameter binding.
Since Spring Data JDBC has no persistence context, Chapter 15's Hibernate-statistics-based monitoring doesn't apply — monitor via straightforward SQL logging instead.
Document which persistence tool (JPA or Spring Data JDBC) a given service uses and WHY — this choice has deep implications for how a new engineer should reason about save/load behavior.
- Model this chapter's
Customerentity using both Spring Data JPA and Spring Data JDBC repositories, and compare the code needed to update it in each. - Mutate a loaded Spring Data JDBC entity with no explicit
save()call, and confirm (unlike JPA) the change is NOT persisted. - List three concrete JPA behaviors from earlier chapters that Spring Data JDBC deliberately does not have.
✓ Quick recap
- Spring Data JDBC has no persistence context — no dirty checking, no lazy loading; every
save()is explicit, exactly this chapter's opening example. - This trades JPA's automatic convenience for a smaller, fully explicit mental model.
- Choose Spring Data JDBC for genuinely simple aggregates; JPA earns its complexity for deep, complex object graphs.
Want a visual for this concept?
Generate a diagram tailored to “Spring Data JDBC vs. Spring Data JPA” — the AI picks whichever visual (flowchart, comparison, sequence, etc.) best fits.
Sign in to generate a visual →