Skip to main content
PreparationZone Logo
PrepZone

JPA First-Level and Second-Level Cache

Persistence-context identity map versus Hibernate's shared cache, and why it is not Spring @Cacheable.

Pass checkpoint below

Why this matters

  • "I called findById twice and saw one SQL" is the first-level cache. Interviewers use it to check whether you understand the persistence context.
  • Second-level cache is a frequent follow-up, and the wrong answer is "just put @Cacheable on the entity." That annotation means two different things in Spring and in JPA.
  • A stale L2 cache in BookStore serves yesterday's price after an admin update. Knowing invalidation is the production part of the question.

First-level cache

L1 · Fresher

Inside one transaction, loading book 42 twice returns the same Java instance and hits the database once.

Identity map

L2 · Intermediate

The persistence context guarantees one managed instance per id. Two variables pointing at book 42 are ==.

Second-level cache

L3 · Advanced

Hibernate can store entity state in a shared cache (Caffeine, Ehcache, Redis via JCache) so the next transaction skips the SELECT.

Query cache caution

L4 · Expert

The query cache stores id lists, not entities, and is easy to invalidate on every write. Most BookStore workloads should skip it.

First-level cache

Java
// learn:fragment
@Transactional
public void demonstrateL1(Long id) {
    Book first = repository.findById(id).orElseThrow();
    Book second = repository.findById(id).orElseThrow();
    // one SELECT; first == second
    first.setPrice(new BigDecimal("12.00"));
    // second.getPrice() is already 12.00 — same instance
}

The cache lives on the EntityManager bound to the current transaction. It is not shared with other requests, other threads, or the next transaction.

What the first-level cache guarantees

  • Repeatable read of managed instances — Re-reading the same id in the transaction returns the in-memory object, including unsaved changes.
  • No extra SELECT — The second findById for that id does not hit PostgreSQL.
  • Automatic discard — Commit or rollback closes the persistence context. The next request starts empty.
  • Not a performance product feature — It exists for correctness of dirty checking. Do not design APIs around it.

entityManager.clear() drops the first-level cache and detaches every managed entity. Bulk imports do this on a batch boundary so the context does not hold a million books.

entityManager.refresh(book) forces a SELECT and overwrites in-memory state. Use it when a database trigger or another transaction changed the row and you must see the new values inside this transaction.

The rest of this guide is part of Pro

Free covers every fresher guide in full. Pro unlocks Senior, Staff and Architect guides across every track.

Still to come · 4 sections

  • First-level cache does not cross transactions
  • Second-level cache
  • L2 versus Spring Cache
  • Query cache

Test yourself

Answer these before moving on — recall is what makes it stick.