Intermediate 3 min · March 09, 2026

Hibernate Caching — L2 Stale Data from Native SQL Batch

20-minute-old prices from L2 cache after native SQL batch - debug stale entities, empty query cache, and high memory with eviction strategies.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
Quick Answer
  • Hibernate L1 cache is the Session-level identity map, always on, cannot be disabled
  • L2 cache is the SessionFactory-level shared cache, optional, requires explicit configuration with a provider
  • Query cache stores query result IDs, not entities; entity cache must be enabled for efficiency
  • Performance: L1 offers near-zero overhead, L2 adds 0.5-2ms per cache hit depending on provider
  • Production pitfall: L2 cache without proper invalidation leads to stale data when external updates bypass Hibernate

Hibernate Caching — First and Second Level is a fundamental concept in Java persistence development. It serves as a mechanism to minimize expensive database hits by keeping frequently accessed entities in memory. Understanding the distinction between these layers is critical for building high-performance, scalable Spring Boot applications.

In this guide, we'll break down exactly what Hibernate Caching is, why the two-level architecture was designed to balance isolation and shared access, and how to configure them correctly in production-grade environments. We will explore how the Persistence Context manages state and how external providers like Ehcache 3 or Hazelcast plug into the Hibernate lifecycle to provide distributed caching capabilities.

By the end, you'll have both the conceptual understanding and practical code examples to implement Hibernate caching strategies with confidence, ensuring your 'io.thecodeforge' microservices handle high-traffic loads with minimal latency.

What Is Hibernate Caching — First and Second Level and Why Does It Exist?

The First Level Cache (L1) is mandatory and associated with the Session object. It ensures that within a single transaction, the same entity is not fetched from the database multiple times. This is also known as the 'Persistence Context' or 'Identity Map' pattern.

The Second Level Cache (L2) is optional and exists at the SessionFactory level, shared across all sessions. It solves the problem of cross-session data redundancy and reduces the load on the database for read-heavy applications. While L1 is short-lived (lasting only as long as the transaction), L2 is long-lived and typically managed by a third-party provider. This architecture allows Hibernate to balance data consistency (L1) with global performance optimization (L2).

Common Mistakes and How to Avoid Them

When learning Hibernate Caching, most developers fall into the trap of over-caching or failing to manage the cache lifecycle. A frequent 'gotcha' is performing bulk updates directly via SQL (Native or JPQL); this bypasses the Hibernate cache, leading to 'stale data' where the cache holds old values while the database has been updated.

At io.thecodeforge, we advocate for the 'Cache-Aside' or 'Read-Through' mindset. Another mistake is enabling L2 cache for highly volatile data. If an entity changes every second, the overhead of invalidating the L2 cache across a cluster (if using Hazelcast/Redis) will actually make your application slower than hitting the database directly.

Query Cache – What It Really Caches and When to Use It

The Query Cache stores the identifiers of entities returned by a query, not the entity data itself. When a cached query is re-executed, Hibernate loads entity identifiers from the cache, then looks up each entity in the L2 entity cache (or DB if missing). This means Query Cache is only useful when Entity Cache is also enabled for the same entities.

Common misconception: enabling hibernate.cache.use_query_cache alone will reduce DB hits. It won't – you'll still hit the DB for each entity if L2 entity cache is off. The real gain comes from combining both: query cache avoids the SQL parse & fetch, entity cache avoids the row load.

Production reality: Query Cache works best for low-churn data like reference tables (countries, status codes). For high-volume transactional data, the overhead of invalidation often outweighs the benefit.

Cache Concurrency Strategies – Choosing the Right Lock Mode

Hibernate provides four CacheConcurrencyStrategy options: READ_ONLY, READ_WRITE, NONSTRICT_READ_WRITE, and TRANSACTIONAL. Each has a different trade-off between consistency and throughput.

  • READ_ONLY: assumes data never changes. Fastest, no locking. Exception thrown if any mutation attempted.
  • READ_WRITE: uses soft locks (a version field or timestamp) to ensure one writer at a time. Good balance for moderate contention.
  • NONSTRICT_READ_WRITE: no locks – writers update cache directly. Readers may see stale data briefly. Suitable for data that can tolerate eventual consistency.
  • TRANSACTIONAL: uses JTA transactions for atomic updates. Requires a cache provider that supports JTA (e.g., Infinispan). Highest consistency, highest overhead.

The wrong choice leads to performance nightmares or runtime errors. For an e-commerce pricing service with frequent updates, READ_WRITE with a version column is the safest bet.

Production Monitoring, Eviction, and Cache Tuning

Once L2 cache is in production, you need to monitor hit ratios, eviction policies, and memory usage. Enable Hibernate statistics via hibernate.generate_statistics=true (or spring.jpa.properties.hibernate.generate_statistics=true). Then register a StatisticsImplementor bean to log cache metrics periodically.

Key metrics to track
  • SecondLevelCacheHitCount: ratio tells you cache effectiveness
  • SecondLevelCacheMissCount: high miss rate means wrong caching or TTL too short
  • EntityLoadCount vs EntityFetchCount: compare cached vs DB loads

Eviction policies (LFU, LRU, TTL) should be configured per region. For volatile entities like user sessions, set TTL to minutes. For static catalog data, use size-limited LFU cache with hours-long TTL.

Common tuning mistake: default configs from cache providers are often too forgiving. Ehcache's default max heap entries is 10,000 per region – that's enough to consume gigabytes if each entity is large. Set explicit limits.

FeatureFirst Level Cache (L1)Second Level Cache (L2)
ScopeSession Level (Private to one thread)SessionFactory Level (Shared across threads)
AvailabilityMandatory (Always on, cannot disable)Optional (Must be explicitly enabled)
LifecycleEnds with the Session/TransactionEnds with the SessionFactory/Application
PerformanceExtremely Fast (Local HashMap)Fast (Provider-dependent, often off-heap)
Stale Data RiskLow (Short-lived isolation)Higher (Requires robust eviction policies)
Data StorageStores entity objectsStores hydrated state (deconstructed data)

Key Takeaways

  • L1 cache is the Session-level buffer that ensures repeatable reads and identity preservation within a single transaction.
  • L2 cache is the cross-session store that drastically reduces database load for common entities across the entire application lifecycle.
  • The Query Cache does not store entities; it stores the IDs of entities matching your query. You must have Entity Caching enabled for Query Caching to be truly efficient.
  • Always use a mature Cache Provider (like Ehcache 3) to manage the memory footprint, eviction (LRU/LFU), and TTL of your L2 cache.
  • Be wary of stale data: ensure that any external database modifications are accounted for in your eviction strategy or via manual cache eviction.
  • Cache concurrency strategies must match data volatility: READ_ONLY for static, READ_WRITE for mutable with versioning, NONSTRICT for eventual consistency.
  • Monitor cache hit ratios in production – high miss rates indicate misconfiguration or wrong caching target.

Common Mistakes to Avoid

  • Not clearing the Session in large batch jobs
    Symptom: Processing 100k records in one transaction causes OutOfMemoryError as L1 cache grows unbounded.
    Fix: Call session.flush() and session.clear() every 50-100 records to free L1 cache.
  • Forgetting to enable Query Caching explicitly
    Symptom: Even with L2 entity cache enabled, repeated queries still hit the database.
    Fix: Set hibernate.cache.use_query_cache=true AND call setHint("org.hibernate.cacheable", true) on each query or criteria.
  • Using L2 cache for tables frequently updated by external systems
    Symptom: Entities appear stale because Hibernate's L2 cache is unaware of out-of-band DB changes (e.g., legacy stored procedures, direct SQL).
    Fix: Evict affected cache regions after external updates, or use shorter TTL and avoid caching highly volatile entities.
  • Choosing the wrong CacheConcurrencyStrategy
    Symptom: Runtime exception when using READ_ONLY for mutable entities, or transaction timeouts with TRANSACTIONAL without JTA support.
    Fix: Match strategy to data lifecycle: READ_ONLY for static, READ_WRITE for most mutable, NONSTRICT_READ_WRITE for eventual consistency, TRANSACTIONAL only with JTA.

Interview Questions on This Topic

  • QExplain the 'N+1 Select Problem' and how the First Level Cache helps (or fails to help) in resolving it.SeniorReveal
    The N+1 problem occurs when Hibernate issues 1 query to fetch the parent entities (usually via a query) and then N additional queries to load each child collection. L1 cache helps within a single session: if the same entity is accessed multiple times, L1 prevents repeated DB hits. But L1 does not prevent N+1 across different parent-child fetches. To solve N+1, use JOIN FETCH, batch fetching, or entity graphs. L2 cache can mitigate repeated N+1 across sessions by caching child entities, but the initial load still triggers N queries. The proper fix is to fetch children eagerly in the same query.
  • QWhat is the difference between session.evict(entity) and session.clear() in Hibernate? When would you use one over the other in a production environment?Mid-levelReveal
    session.evict(entity) removes a single entity from the L1 cache (Persistence Context). session.clear() evicts all managed entities from the L1 cache. Use evict when you need to free memory for a specific large entity while keeping others in context. Use clear in batch processing to flush and then release all managed objects before the next batch, preventing OOM. In production, avoid excessive evict/clear as it breaks repeatable reads within the same transaction.
  • QCompare the four CacheConcurrencyStrategy types: READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, and TRANSACTIONAL. Which is best for a high-concurrency e-commerce site?SeniorReveal
    READ_ONLY: fastest, no locking, but only for immutable data. NONSTRICT_READ_WRITE: no locking, writers update cache directly, readers may see stale data; good for eventual consistency. READ_WRITE: uses soft locks (version/timestamp) to allow one writer at a time; best balance for moderate to high concurrency with consistency. TRANSACTIONAL: uses JTA transactions for atomic updates; highest consistency but requires JTA provider and adds latency. For an e-commerce site where pricing and stock need strong consistency, use READ_WRITE. For product descriptions that seldom change, READ_ONLY is fine.
  • QHow does the L2 cache handle entity relationships (One-to-Many, Many-to-One)? Does caching a parent entity automatically cache its children?Mid-levelReveal
    No, caching a parent entity does NOT automatically cache its associated children. Each entity must be individually annotated with @Cacheable to be stored in L2 cache. If you cache a parent entity but not its child collection, loading the parent still triggers DB queries for the children. For collection caching, use @Cache(usage = ...) on the association field (e.g., @OneToMany). Be aware that cached collections are invalidated as a whole if any element changes, causing high churn for large collections.
  • QHow do you ensure the L2 cache is invalidated when a bulk update query is executed? Explain the use of the @Modifying and @Query annotation with clearAutomatically = true in Spring Data JPA.SeniorReveal
    @Modifying(clearAutomatically = true) tells Spring Data JPA to automatically clear the L1 cache after a bulk update. However, it does NOT affect the L2 cache. To also invalidate L2, you must manually evict the affected region after the bulk operation. For example: `` @Modifying(clearAutomatically = true) @Query("UPDATE Product p SET p.price = :price WHERE p.id = :id") int updatePrice(@Param("id") Long id, @Param("price") BigDecimal price); // After calling this, also call: entityManager.getEntityManagerFactory().getCache().evict(Product.class); ``

Frequently Asked Questions

Can I disable the First Level Cache?

No, the L1 cache is always enabled in Hibernate. It is part of the Persistence Context and cannot be disabled. However, you can clear it programmatically using session.clear() or evict specific entities with session.evict(entity).

Does the L2 cache work across multiple application instances?

Not by default. Standard L2 cache is local to the SessionFactory (per JVM). To share cache across instances, you need a distributed cache provider like Hazelcast, Infinispan, or Redis (via Redisson). Configure the region factory to use a clustered backend.

What happens if I use `@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)` on an entity that gets updated?

Hibernate will throw a HibernateException at commit time because it detects a mutation on a read-only entity. This is caught only during the flush phase, so you won't see it at save(), only at transaction commit.

Does Hibernate's Query Cache store the actual entity data?

No. The Query Cache stores only the identifiers (primary key values) of the entities that matched the query. When the cached query is reused, Hibernate fetches each entity by ID from the L2 entity cache or database. Therefore, Query Cache is only beneficial when Entity Cache is also enabled.

How do I clear the L2 cache programmatically?

Use sessionFactory.getCache().evictEntityRegion(MyEntity.class) to evict an entire region, or evictAllRegions() for all regions. In Spring Boot, inject EntityManagerFactory and unwrap to SessionFactory to access caching methods.

🔥

That's Hibernate & JPA. Mark it forged?

3 min read · try the examples if you haven't

Previous
HQL vs JPQL vs Native SQL
6 / 7 · Hibernate & JPA
Next
Hibernate N+1 Problem and How to Fix It