How to Configure the Second-Level Cache
Configuring the second-level cache involves selecting a provider, configuring Hibernate, setting up cache regions, and defining caching strategies for each entity.
Overview
Configuring the second-level cache involves selecting a provider, configuring Hibernate, setting up cache regions, and defining caching strategies for each entity.
Junior Level
Basic Setup
To set up L2 cache, you need to complete 3 steps:
- Enable cache in configuration
- Choose a provider (Ehcache, Hazelcast, Redis)
- Mark entities with
@Cacheableannotation
# 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
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@Id
private Long id;
private String name;
}
Caching Modes
# All entities cached by default
javax.persistence.sharedCache.mode: ALL
# Only those marked @Cacheable (recommended)
javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
# All except those marked @Cacheable(false)
javax.persistence.sharedCache.mode: DISABLE_SELECTIVE
# None (cache disabled)
javax.persistence.sharedCache.mode: NONE
Middle Level
When NOT to Use L2 Cache
- Write-heavy workload - constant cache invalidation is more expensive than SELECT from DB
- Highly normalized data with frequent JOINs - cache is ineffective
- Multi-node cluster with frequent updates - invalidation traffic exceeds the benefit
Ehcache Configuration
<!-- ehcache.xml in src/main/resources -->
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xmlns='http://www.ehcache.org/v3'>
<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>
Programmatic 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();
Collections in Cache
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@OneToMany(mappedBy = "user")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Order> orders; // collection caching
}
Common Mistakes
// No TTL - cache grows infinitely
// Solution: always set TTL in provider configuration
// Caching all entities
// Solution: cache only frequently read, rarely modified
// Wrong strategy
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Order { } // Orders change frequently
Senior Level
Clustered Cache
# Hazelcast for cluster
spring:
jpa:
properties:
hibernate:
cache:
region:
factory_class: com.hazelcast.hibernate.HazelcastCacheRegionFactory
# Redis
spring:
jpa:
properties:
hibernate:
cache:
region:
factory_class: com.redislabs.hibernate.redisson.cache.impl.RedissonRegionFactory
Clustered cache problems:
- Eventual consistency (not strong) - data on different cluster nodes becomes the same not instantly, but after some time. During this period, one node may see old data, another - new.
- Network latency during replication
- Conflicts on simultaneous update
- Need for cache invalidation strategy
Region Configuration
@Entity
@Cacheable
@org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "userCache" // region name
)
public class User { }
<!-- Different settings for different regions -->
<cache alias="userCache">
<heap>5000</heap>
<expiry><ttl unit="minutes">15</ttl></expiry>
</cache>
<cache alias="countryCache">
<heap>500</heap>
<expiry><ttl unit="hours">48</ttl></expiry>
</cache>
Production Monitoring
Statistics stats = entityManagerFactory
.unwrap(SessionFactory.class)
.getStatistics();
// L2 cache metrics
long hitCount = stats.getSecondLevelCacheHitCount();
long missCount = stats.getSecondLevelCacheMissCount();
long putCount = stats.getSecondLevelCachePutCount();
long evictionCount = stats.getSecondLevelCacheEvictionCount();
double hitRatio = (double) hitCount / (hitCount + missCount);
// Logging
log.info("L2 Cache: hits={}, misses={}, ratio={:.2f}",
hitCount, missCount, hitRatio);
// hitRatio > 0.8 is usually good for read-heavy workloads; for write-heavy workloads even 0.5 may be normal.
Query Cache
spring:
jpa:
properties:
hibernate:
cache:
use_query_cache: true
@QueryHint(name = "org.hibernate.cacheable", value = "true")
@Query("SELECT u FROM User u WHERE u.status = :status")
List<User> findByStatus(@Param("status") String status);
// Important: query cache stores only entity IDs
// Actual entities come from L2 cache
Best Practices
TTL for each region
Heap limit to prevent memory leak
Clustered cache for multi-node applications
Monitor hit ratio
ENABLE_SELECTIVE mode
READ_ONLY for reference data
Without TTL and limits
Cache for frequently changing data
Without hit ratio monitoring
Query cache without L2 cache
INVALIDATE_ALL on every change
Interview Cheat Sheet
Must know:
- 3 setup steps: enable cache, choose provider, mark entities with @Cacheable
- Modes: ENABLE_SELECTIVE (recommended), ALL, DISABLE_SELECTIVE, NONE
- Caching strategies: READ_ONLY for reference data, READ_WRITE for changing data
- Ehcache configuration: heap size, offheap, TTL for each region
- Clustered cache: Hazelcast/Redis, eventual consistency, network overhead
- Query cache is useless without L2 cache - stores only entity IDs
Frequent follow-up questions:
- Which caching mode to choose? ENABLE_SELECTIVE - only those marked @Cacheable, best control
- How to set different TTLs for different entities? Via regions: region = “userCache” and different settings in ehcache.xml
- What happens when an entity is updated in a cluster? Invalidation message broadcast to all nodes, next request - miss
- How to monitor in production? Statistics: hitCount, missCount, hitRatio, evictionCount
Red flags (DO NOT say):
- “I enable cache without TTL” - memory leak
- “READ_ONLY for Order” - orders change frequently
- “Query cache without L2 cache” - useless
- “ENABLE_SELECTIVE but don’t mark @Cacheable” - cache doesn’t work
Related topics:
- [[10. What is the Second-Level Cache and When to Use It]]
- [[9. What is the First-Level Cache in Hibernate]]
- [[12. What is Dirty Checking in Hibernate]]
- [[1. What is the N+1 Problem and How to Solve It]]