What is the Second-Level Cache and When to Use It
The second-level cache (L2 cache) is a cache at the EntityManagerFactory level, shared between all sessions. Unlike L1 cache, it survives session closures and can be used by mul...
Overview
The second-level cache (L2 cache) is a cache at the EntityManagerFactory level, shared between all sessions. Unlike L1 cache, it survives session closures and can be used by multiple transactions.
Why: when 100 users simultaneously request the same reference data (e.g., a list of countries), L1 cache is useless - each session has its own cache. L2 cache loads countries ONCE and shares between all.
Junior Level
What is L2 Cache
The second-level cache is a cache shared between sessions. If one session loads an entity, another session can get it from L2 cache without accessing the DB.
Session 1: loads User(1) -> L1 cache + L2 cache
Session 1: closed -> L1 cache deleted, L2 cache remains
Session 2: loads User(1) -> L2 cache (NO DB!)
L1 vs L2 Cache
| Characteristic | L1 Cache | L2 Cache |
|---|---|---|
| Level | EntityManager | EntityManagerFactory |
| By default | Enabled | Needs to be enabled |
| Lifecycle | One session | Entire application |
| Sharing | No | Between sessions |
| Provider | Built-in | Ehcache, Hazelcast, Redis |
When L2 Cache is Useful
Suitable when:
- Data rarely changes (reference data, countries, currencies)
- Many reads, few writes
- Multiple application instances
Not suitable when:
- Data changes frequently (orders, logs)
- Absolute data freshness is required
- More writes than reads
Middle Level
When NOT to Use L2 Cache
L2 cache does NOT work with:
- Native SQL queries (Hibernate doesn’t know which entities are affected)
- Query cache for pagination (cached separately)
- EntityManager.clear() does not clear L2 cache
Configuration
<!-- Dependency for JCache provider -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jcache</artifactId>
</dependency>
# application.yml
spring:
jpa:
properties:
hibernate:
cache:
use_second_level_cache: true
region:
factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
In Hibernate 5.x, javax.persistence is used. In Hibernate 6+ / Jakarta EE, replace with jakarta.persistence and update factory class paths.
// Marking entity for caching
@Entity
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@Id
private Long id;
private String name;
}
Caching Strategies
READ_ONLY:
- Read-only, data never changes
- Fastest strategy
- For reference data, countries, currencies
READ_WRITE:
- Read and write with synchronization
- Uses soft locks
- For moderately changing data
NONSTRICT_READ_WRITE:
- Read and write without strict synchronization
- May return stale data
- For rarely changing data
TRANSACTIONAL:
- Full transactional consistency
- Requires XA transactions
- For critically important data
Cache Management
Cache cache = entityManager.getEntityManagerFactory().getCache();
// Evict specific entity
cache.evict(User.class, userId);
// Evict all entities of type
cache.evict(User.class);
// Evict everything
cache.evictAll();
Common Mistakes
// Caching frequently changing data
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Order { }
// Orders change frequently -> stale data!
// No TTL
// Cache grows infinitely -> memory leak
// Solution: always set TTL
Senior Level
Soft locks - a soft locking mechanism: Hibernate marks the cache entry as “updating”, other threads wait. Unlike hard DB locking, this doesn’t lock the table row.
XA transactions - two-phase transactions ensuring atomicity of operations both in the DB and in the cache.
Eventual consistency - consistency achieved eventually: after an update, data in the cache on different nodes becomes the same not instantly, but after some time.
Invalidation Mechanism
L2 cache invalidation occurs on:
- UPDATE/DELETE of entity -> eviction from cache
- Explicit evict() -> removal from cache
- TTL expiration -> automatic removal
- Cluster replication -> replication between nodes
Clustered Cache
# Hazelcast L2 cache for cluster
spring:
jpa:
properties:
hibernate:
cache:
region:
factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory
For multiple application instances:
- Cache replication between nodes (Hazelcast, Redis)
- Eventual consistency (not strong)
- Network overhead during replication
- Must be considered in design
When User is updated on Node 1: (1) Hibernate updates DB;
(2) invalidates entry in L2 cache Node 1;
(3) Hazelcast broadcasts invalidation message to Node 2, 3;
(4) Next request on Node 2 misses cache and loads from DB.
Production Monitoring
// Hibernate Statistics
Statistics stats = entityManagerFactory.unwrap(SessionFactory.class).getStatistics();
// L2 cache metrics
long hitCount = stats.getSecondLevelCacheHitCount();
long missCount = stats.getSecondLevelCacheMissCount();
double hitRatio = (double) hitCount / (hitCount + missCount);
// For production monitoring
// hitRatio > 0.8 - excellent, cache used effectively
// hitRatio < 0.5 - check: are correct entities chosen for caching, is cache size sufficient, is TTL too short
Ehcache Configuration
<!-- ehcache.xml -->
<config>
<cache alias="com.example.User">
<heap unit="entries">1000</heap>
<offheap unit="MB">50</offheap>
<expiry>
<ttl unit="minutes">30</ttl>
</expiry>
</cache>
<cache alias="com.example.Country">
<heap unit="entries">500</heap>
<expiry>
<ttl unit="hours">24</ttl>
</expiry>
</cache>
</config>
Query Cache vs L2 Cache
Query cache stores: “query X with parameters Y returned entities with IDs [1,2,3]”. L2 cache stores: “entity User(1) = {name: John, …}”. They work together: Query cache gives IDs, L2 cache gives data. Query cache is useless without L2 cache.
Best Practices
READ_ONLY for reference data and constants
READ_WRITE for moderately changing data
Monitor hit/miss ratio
Set TTL for each region
Limit heap size
Invalidate on updates
Clustered cache for multi-node
For frequently changing data
Without hit ratio monitoring
Without TTL and memory limits
Cache for entities with N+1 problem
Using as a replacement for proper architecture
Interview Cheat Sheet
Must know:
- L2 cache - at EntityManagerFactory level, shared between sessions
- Needs to be enabled: choose provider (Ehcache, Hazelcast, Redis), configure
- Strategies: READ_ONLY (reference data), READ_WRITE (moderately changing), NONSTRICT_READ_WRITE, TRANSACTIONAL
- Useful for: rarely changing data, many reads/few writes, reference data
- Query cache stores entity IDs, actual data - from L2 cache (useless without L2)
- Monitoring: hitRatio > 0.8 excellent, < 0.5 - check configuration
Frequent follow-up questions:
- L1 vs L2 cache? L1 - within session (always enabled), L2 - between sessions (needs enabling)
- When does L2 cache NOT work? Native SQL queries, query cache for pagination, after EntityManager.clear()
- How does clustered cache work? Replication between nodes (Hazelcast, Redis), eventual consistency
- What are soft locks? Soft locking - Hibernate marks entry as “updating”, other threads wait
Red flags (DO NOT say):
- “I cache all entities indiscriminately” - only cache read-heavy
- “No TTL - cache grows infinitely” - memory leak
- “L2 cache for Order/Log” - frequently changing data, cache ineffective
- “Query cache without L2 cache” - useless, stores only IDs
Related topics:
- [[9. What is the First-Level Cache in Hibernate]]
- [[11. How to Configure the Second-Level Cache]]
- [[1. What is the N+1 Problem and How to Solve It]]
- [[13. How Does the Flush Mechanism Work in Hibernate]]