← Назад к вопросам

Какие существуют уровни кэширования в Hibernate

2.3 Middle🔥 191 комментариев
#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Уровни кэширования в Hibernate

Общий обзор

Hibernate имеет два основных уровня кэширования:

  1. First-Level Cache (L1) — Session-level cache (встроена)
  2. 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 CacheL2 CacheQuery Cache
УровеньSessionSessionFactorySessionFactory
Всегда включена✅ Да❌ Нет❌ Нет
Разделяется между сессиями❌ Нет✅ Да✅ Да
РасположениеJVM MemoryJVM/ExternalExternal
Скорость⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Потребление памяти⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Конфигурация-⭐⭐⭐⭐⭐⭐
Рассинхронизация✅ (риск)✅ (высокий)

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:

  1. L1 Cache (Session) — всегда есть, быстра, привязана к сессии
  2. L2 Cache (SessionFactory) — опциональна, разделяется, нужна конфигурация
  3. Query Cache — кэширует результаты запросов, рискует рассинхронизацией

Рекомендация:

  • Используй L1 Cache (автоматически)
  • Включай L2 Cache для статических справочников
  • Осторожно с Query Cache (проблема с инвалидацией)
  • Всегда инвалидируй кэш при bulk операциях
Какие существуют уровни кэширования в Hibernate | PrepBro