Question 11 · Section 16

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.

Language versions: English Russian Ukrainian

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:

  1. Enable cache in configuration
  2. Choose a provider (Ehcache, Hazelcast, Redis)
  3. Mark entities with @Cacheable annotation
# 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

  1. Write-heavy workload - constant cache invalidation is more expensive than SELECT from DB
  2. Highly normalized data with frequent JOINs - cache is ineffective
  3. 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]]