Как настроить кэш второго уровня
Настройка кэша второго уровня включает выбор провайдера, конфигурацию Hibernate, настройку регионов кэша и определение стратегий кэширования для каждой сущности.
Обзор
Настройка кэша второго уровня включает выбор провайдера, конфигурацию Hibernate, настройку регионов кэша и определение стратегий кэширования для каждой сущности.
🟢 Junior Level
Базовая настройка
Для настройки L2 cache нужно выполнить 3 шага:
- Включить кэш в конфигурации
- Выбрать провайдер (Ehcache, Hazelcast, Redis)
- Пометить сущности аннотацией
@Cacheable
# 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;
}
Режимы кэширования
# Все сущности кэшируются по умолчанию
javax.persistence.sharedCache.mode: ALL
# Только помеченные @Cacheable (рекомендуется)
javax.persistence.sharedCache.mode: ENABLE_SELECTIVE
# Все кроме помеченных @Cacheable(false)
javax.persistence.sharedCache.mode: DISABLE_SELECTIVE
# Никакие (кэш отключён)
javax.persistence.sharedCache.mode: NONE
🟡 Middle Level
Когда НЕ использовать L2 cache
- Write-heavy workload — постоянная инвалидация кэша дороже чем SELECT из БД
- Высоко нормализованные данные с частыми JOIN — кэш неэффективен
- Многоузловой кластер с частыми обновлениями — invalidation traffic превышает пользу
Настройка Ehcache
<!-- ehcache.xml в 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>
Управление кэшем программно
Cache cache = entityManager.getEntityManagerFactory().getCache();
// Evict конкретную сущность
cache.evict(User.class, userId);
// Evict все сущности типа
cache.evict(User.class);
// Evict всё
cache.evictAll();
Коллекции в кэше
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@OneToMany(mappedBy = "user")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Order> orders; // кэширование коллекции
}
Типичные ошибки
// ❌ Нет TTL — кэш растёт бесконечно
// Решение: всегда задавайте TTL в конфигурации провайдера
// ❌ Кэш для всех сущностей
// Решение: кэшируйте только часто читаемые, редко изменяемые
// ❌ Неправильная стратегия
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Order { } // ❌ Orders часто меняются
🔴 Senior Level
Кластерный кэш
# Hazelcast для кластера
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
Проблемы кластерного кэша:
- Eventual consistency (не strong) — данные в разных узлах кластера станут одинаковыми не мгновенно, а через некоторое время. В этот момент один узел может видеть старые данные, другой — новые.
- Network latency при репликации
- Конфликты при одновременном обновлении
- Необходимость стратегии инвалидации кэша
Настройка регионов
@Entity
@Cacheable
@org.hibernate.annotations.Cache(
usage = CacheConcurrencyStrategy.READ_WRITE,
region = "userCache" // имя региона
)
public class User { }
<!-- Разные настройки для разных регионов -->
<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
Statistics stats = entityManagerFactory
.unwrap(SessionFactory.class)
.getStatistics();
// Метрики L2 cache
long hitCount = stats.getSecondLevelCacheHitCount();
long missCount = stats.getSecondLevelCacheMissCount();
long putCount = stats.getSecondLevelCachePutCount();
long evictionCount = stats.getSecondLevelCacheEvictionCount();
double hitRatio = (double) hitCount / (hitCount + missCount);
// Логирование
log.info("L2 Cache: hits={}, misses={}, ratio={:.2f}",
hitCount, missCount, hitRatio);
// hitRatio > 0.8 обычно хорошо для read-heavy нагрузки; для write-heavy workload даже 0.5 может быть нормой.
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);
// Важно: query cache хранит только ID сущностей
// Сами сущности берутся из L2 cache
Best Practices
✅ TTL для каждого region
✅ Heap limit для предотвращения memory leak
✅ Кластерный кэш для multi-node приложений
✅ Мониторинг hit ratio
✅ ENABLE_SELECTIVE mode
✅ READ_ONLY для справочников
❌ Без TTL и лимитов
❌ Кэш для часто меняющихся данных
❌ Без мониторинга hit ratio
❌ Query cache без L2 cache
❌ INVALIDATE_ALL при каждом изменении
🎯 Шпаргалка для интервью
Обязательно знать:
- 3 шага настройки: включить кэш, выбрать провайдера, пометить сущности @Cacheable
- Режимы: ENABLE_SELECTIVE (рекомендуется), ALL, DISABLE_SELECTIVE, NONE
- Стратегии кэширования: READ_ONLY для справочников, READ_WRITE для изменяемых данных
- Настройка Ehcache: heap size, offheap, TTL для каждого региона
- Кластерный кэш: Hazelcast/Redis, eventual consistency, network overhead
- Query cache бесполезен без L2 cache — хранит только ID сущностей
Частые уточняющие вопросы:
- Какой режим кэширования выбрать? ENABLE_SELECTIVE — только помеченные @Cacheable, лучший контроль
- Как настроить разные TTL для разных сущностей? Через регионы: region = “userCache” и разные настройки в ehcache.xml
- Что происходит при обновлении сущности в кластере? Invalidation message рассылается на все ноды, next запрос — miss
- Как мониторить в production? Statistics: hitCount, missCount, hitRatio, evictionCount
Красные флаги (НЕ говорить):
- «Включаю кэш без TTL» — memory leak
- «READ_ONLY для Order» — заказы часто меняются
- «Query cache без L2 cache» — бесполезен
- «ENABLE_SELECTIVE но не помечаю @Cacheable» — кэш не работает
Связанные темы:
- [[10. Что такое кэш второго уровня и когда его использовать]]
- [[9. Что такое кэш первого уровня в Hibernate]]
- [[12. Что такое dirty checking в Hibernate]]
- [[1. Что такое проблема N+1 и как её решить]]