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.
- 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.
- 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.
| Feature | First Level Cache (L1) | Second Level Cache (L2) |
|---|---|---|
| Scope | Session Level (Private to one thread) | SessionFactory Level (Shared across threads) |
| Availability | Mandatory (Always on, cannot disable) | Optional (Must be explicitly enabled) |
| Lifecycle | Ends with the Session/Transaction | Ends with the SessionFactory/Application |
| Performance | Extremely Fast (Local HashMap) | Fast (Provider-dependent, often off-heap) |
| Stale Data Risk | Low (Short-lived isolation) | Higher (Requires robust eviction policies) |
| Data Storage | Stores entity objects | Stores 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: Callandsession.flush()every 50-100 records to free L1 cache.session.clear() - Forgetting to enable Query Caching explicitly
Symptom: Even with L2 entity cache enabled, repeated queries still hit the database.
Fix: Sethibernate.cache.use_query_cache=trueAND callsetHint("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
- 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 - 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
- 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
- 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
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 or evict specific entities with session.clear()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 , only at transaction commit.save()
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