Какие существуют уровни кэширования в Hibernate
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Уровни кэширования в Hibernate
Общий обзор
Hibernate имеет два основных уровня кэширования:
- First-Level Cache (L1) — Session-level cache (встроена)
- Second-Level Cache (L2) — SessionFactory-level cache (опциональна)
А также: Query Result Cache для кэширования результатов запросов.
Application
↓
[First-Level Cache] ← Session (всегда есть)
↓
[Second-Level Cache] ← SessionFactory (опционально)
↓
[Query Result Cache] ← SessionFactory (опционально)
↓
Database
First-Level Cache (L1) — Session Cache
Определение
First-Level Cache привязана к сессии (Session). Хранит объекты во время жизни сессии.
Характеристики
// First-Level Cache активна по умолчанию
public class HibernateL1Demo {
public static void main(String[] args) {
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// Запрос 1: идёт в БД
User user1 = session.get(User.class, 1L);
System.out.println("User 1: " + user1.getName());
// Запрос в БД: SELECT * FROM users WHERE id = 1
// user1 попал в L1 Cache
// Запрос 2: ВОЗВРАЩАЕТСЯ ИЗ L1 КЭША
User user2 = session.get(User.class, 1L);
System.out.println("User 2: " + user2.getName());
// НИКАКОГО запроса в БД! Используется объект из кэша
// user1 == user2 (один и тот же объект в памяти)
tx.commit();
session.close();
// После close() L1 Cache очищается
}
}
Как работает L1 Cache
Session session1 = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
Transaction tx2 = session2.beginTransaction();
// Каждая сессия имеет свой кэш
User user1_s1 = session1.get(User.class, 1L); // В L1 кэш session1
User user1_s2 = session2.get(User.class, 1L); // В L1 кэш session2
// Это разные объекты!
assert user1_s1 != user1_s2; // true
assert user1_s1.getName().equals(user1_s2.getName()); // true
// Если изменим одну копию
user1_s1.setName("Alice Updated");
session1.update(user1_s1);
// Вторая копия не изменится
assert user1_s2.getName().equals("Alice"); // Старое значение
Типы операций в L1 Cache
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// 1. GET (читаем объект)
User user = session.get(User.class, 1L); // В кэш
// 2. UPDATE (обновляем)
user.setName("New Name");
session.update(user); // Обновляется в кэше
// 3. DELETE (удаляем)
session.delete(user); // Помечается как удалённый в кэше
// 4. SAVE (создаём)
User newUser = new User();
newUser.setName("John");
session.save(newUser); // В кэш
tx.commit();
session.close();
Управление L1 Cache
// Очистить весь кэш сессии
session.clear();
// Очистить конкретный объект
session.evict(user);
// Обновить объект из БД (игнорируя кэш)
session.refresh(user);
// Flush: синхронизировать состояние с БД
session.flush(); // Отправляет все изменения в БД, но сессия остаёется открытой
// Пример
User user = session.get(User.class, 1L); // В кэш
user.setName("Alice Updated");
session.flush(); // INSERT/UPDATE в БД
// user всё ещё в кэше
session.refresh(user); // Перечитаем из БД (игнорируя кэш)
// user.getName() вернёт значение из БД
Преимущества L1 Cache
✅ Всегда включена — автоматическая оптимизация
✅ Быстро — в памяти JVM
✅ Безопасна — привязана к сессии
✅ Гарантирует идентичность — одна сессия, один объект
Недостатки L1 Cache
❌ Привязана к сессии — разные сессии = разные кэши
❌ Потребляет память — каждая сессия накапливает объекты
❌ Нельзя отключить — всегда работает
Second-Level Cache (L2) — SessionFactory Cache
Определение
Second-Level Cache привязана к SessionFactory. Разделяется между всеми сессиями. Опционально.
Session 1 ──┐
Session 2 ──┼─→ [L2 Cache] ← SessionFactory
Session 3 ──┘
Если объект есть в L2, все сессии его видят
Конфигурация
<!-- hibernate.cfg.xml -->
<hibernate-configuration>
<session-factory>
<!-- Включаем L2 Cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- Выбираем провайдер кэша -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.jcache.JCacheRegionFactory
</property>
<!-- Или используем встроенный кэш -->
<property name="hibernate.cache.region.factory_class">
org.hibernate.cache.internal.EhcacheRegionFactory
</property>
<!-- Какие классы кэшировать -->
<class-cache class="com.example.User" usage="read-write"/>
<class-cache class="com.example.Product" usage="nonstrict-read-write"/>
<!-- Кэшировать коллекции -->
<collection-cache collection="com.example.User.addresses" usage="read-write"/>
</session-factory>
</hibernate-configuration>
Провайдеры кэша
// 1. Ehcache (встроенный)
org.hibernate.cache.internal.EhcacheRegionFactory
// 2. Redis
com.github.debop.hibernate.cache.redis.HibernateRedisRegionFactory
// 3. Infinispan (для cluster)
org.hibernate.cache.infinispan.InfinispanRegionFactory
// 4. Hazelcast
com.hazelcast.hibernate.HazelcastLocalCacheRegionFactory
// 5. Memcached
net.spy.memcached.hibernate.MemcachedCacheProvider
Стратегии L2 Cache
<!-- read-only: не изменяется никогда -->
<class-cache class="com.example.Country" usage="read-only"/>
<!-- read-write: может изменяться, но не часто -->
<class-cache class="com.example.User" usage="read-write"/>
<!-- nonstrict-read-write: может изменяться, риск рассинхронизации -->
<class-cache class="com.example.Product" usage="nonstrict-read-write"/>
<!-- transactional: гарантирует ACID -->
<class-cache class="com.example.Order" usage="transactional"/>
Как работает L2 Cache
// Две разные сессии
Session session1 = sessionFactory.openSession();
Session session2 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
Transaction tx2 = session2.beginTransaction();
// Session 1 загружает пользователя
User user1_s1 = session1.get(User.class, 1L);
// L1 Cache (session1): {User#1 -> user1_s1}
// L2 Cache: {User#1 -> user1_s1}
// Session 2 загружает ТОЖ Е пользователя
User user1_s2 = session2.get(User.class, 1L);
// L1 Cache (session2): {User#1 -> user1_s2}
// Но L2 Cache попадает! (быстрее чем БД)
// user1_s2 скопирован из L2 Cache, но это разные объекты
assert user1_s1 != user1_s2; // true (разные объекты)
assert user1_s1.getName().equals(user1_s2.getName()); // true (но данные одинаковые)
tx1.commit();
tx2.commit();
session1.close();
session2.close();
Управление L2 Cache
// Очистить весь L2 Cache
sessionFactory.getCache().evictAllRegions();
// Очистить конкретный класс
sessionFactory.getCache().evict(User.class);
// Очистить конкретный объект
sessionFactory.getCache().evict(User.class, 1L);
// Очистить коллекцию
sessionFactory.getCache().evict(User.class, "addresses", 1L);
Пример конфигурации с Ehcache
@Configuration
public class HibernateConfig {
@Bean
public Properties hibernateProperties() {
Properties props = new Properties();
props.put("hibernate.cache.use_second_level_cache", "true");
props.put("hibernate.cache.region.factory_class",
"org.hibernate.cache.jcache.JCacheRegionFactory");
props.put("javax.cache.provider",
"org.ehcache.jsr107.EhcacheCachingProvider");
return props;
}
}
// Entity
@Entity
@Cacheable // Кэшировать этот класс
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
@Id
private Long id;
private String name;
@OneToMany(mappedBy = "user")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Order> orders = new HashSet<>();
}
// Entity
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Country {
@Id
private Long id;
private String name; // Не изменяется
}
Преимущества L2 Cache
✅ Разделяется между сессиями — экономит БД
✅ Масштабируемо — Redis, Memcached на отдельном сервере
✅ Кластеризуется — Infinispan, Hazelcast
✅ Значительно снижает нагрузку на БД
Недостатки L2 Cache
❌ Сложность — конфигурация, стратегии
❌ Рассинхронизация — может быть старые данные
❌ Сетевой overhead — если Redis на отдельном сервере
❌ Потребление памяти — нужно выбирать что кэшировать
Query Result Cache
Определение
Кэширует результаты HQL/SQL запросов.
@Configuration
public class HibernateConfig {
@Bean
public Properties hibernateProperties() {
Properties props = new Properties();
props.put("hibernate.cache.use_query_cache", "true");
props.put("hibernate.cache.use_second_level_cache", "true");
return props;
}
}
// Использование
Session session = sessionFactory.openSession();
Query query = session.createQuery("FROM User WHERE age > :age")
.setParameter("age", 18)
.setCacheable(true) // Включаем кэширование
.setCacheRegion("users_over_18"); // Имя кэша
List<User> users = query.list();
// Первый запрос идёт в БД и сохраняется в Query Result Cache
// Следующие вызовы этого же запроса вернут результат из кэша
session.close();
Проблема Query Cache
// Первый запрос
List<User> users = session.createQuery("FROM User")
.setCacheable(true)
.list(); // Сохранено в кэш
// Другая сессия изменила данные
Session session2 = sessionFactory.openSession();
User user = session2.get(User.class, 1L);
user.setName("Updated");
session2.update(user);
session2.flush();
// Первый запрос вернёт СТАРЫЕ данные из кэша!
List<User> users2 = session.createQuery("FROM User")
.setCacheable(true)
.list(); // Из кэша, не актуально!
Инвалидация Query Cache
// Когда обновляются данные, нужно инвалидировать Query Cache
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L);
user.setName("Updated");
session.update(user);
tx.commit();
session.close();
// Query Cache автоматически инвалидируется при update/delete
// Если правильно конфигурирован
Сравнение уровней кэширования
| Характеристика | L1 Cache | L2 Cache | Query Cache |
|---|---|---|---|
| Уровень | Session | SessionFactory | SessionFactory |
| Всегда включена | ✅ Да | ❌ Нет | ❌ Нет |
| Разделяется между сессиями | ❌ Нет | ✅ Да | ✅ Да |
| Расположение | JVM Memory | JVM/External | External |
| Скорость | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Потребление памяти | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Конфигурация | - | ⭐⭐⭐ | ⭐⭐⭐ |
| Рассинхронизация | ❌ | ✅ (риск) | ✅ (высокий) |
Best Practices
// 1. Всегда используй L1 Cache (она встроена)
Session session = sessionFactory.openSession();
User user1 = session.get(User.class, 1L); // БД
User user2 = session.get(User.class, 1L); // L1 Cache
// 2. Включи L2 Cache для часто читаемых данных
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Country { } // Страны не меняются
// 3. Не кэшируй часто изменяемые данные
// ❌ @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
// public class UserSession { } // Изменяется каждую сек
// 4. Query Cache только для стабильных запросов
Query query = session.createQuery(
"FROM Country ORDER BY name"
);
query.setCacheable(true); // Страны не меняются
// 5. Очищай кэш при bulk операциях
Query query = session.createQuery("UPDATE User SET active = false");
query.executeUpdate();
sessionFactory.getCache().evict(User.class); // Очистить L2
// 6. Используй правильные стратегии
// READ_ONLY — данные никогда не меняются
// READ_WRITE — данные меняются, но редко
// NONSTRICT_READ_WRITE — высокий риск рассинхронизации
// TRANSACTIONAL — ACID гарантии (дорого)
Вывод
Three levels of caching in Hibernate:
- L1 Cache (Session) — всегда есть, быстра, привязана к сессии
- L2 Cache (SessionFactory) — опциональна, разделяется, нужна конфигурация
- Query Cache — кэширует результаты запросов, рискует рассинхронизацией
Рекомендация:
- Используй L1 Cache (автоматически)
- Включай L2 Cache для статических справочников
- Осторожно с Query Cache (проблема с инвалидацией)
- Всегда инвалидируй кэш при bulk операциях