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

Что такое кэш в Hibernate?

2.0 Middle🔥 191 комментариев
#ORM и Hibernate#Кэширование и NoSQL

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

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

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

Кэш в Hibernate

Hibernate использует многоуровневую систему кэширования для оптимизации производительности. Это критическая часть ORM фреймворка для снижения количества запросов к БД.

Иерархия кэша в Hibernate

// Session -> First-Level Cache (L1)
//         -> Second-Level Cache (L2)
//         -> Query Cache

public class CacheHierarchy {
    // L1: Session-level (всегда включён)
    // L2: Application-level (опционально)
    // Query: Query results cache (опционально)
}

1. First-Level Cache (Session Cache)

Это встроенный кэш на уровне Session — всегда включён:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    public void demonstrateL1Cache(Long userId) {
        // Запрос 1: SELECT * FROM users WHERE id = 1
        User user1 = userRepository.findById(userId).orElse(null);
        
        // Запрос 2: НЕ вызывает SELECT - берёт из L1 кэша!
        User user2 = userRepository.findById(userId).orElse(null);
        
        // Истины: user1 == user2 (один объект)
        assert user1 == user2;
    }
}

// В Hibernate
public class L1CacheExample {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        
        // First SELECT
        User user1 = session.get(User.class, 1L);
        // SQL: SELECT * FROM users WHERE id = 1
        
        // Second GET — из кэша L1
        User user2 = session.get(User.class, 1L);
        // SQL: не выполняется!
        
        // Очистка L1 кэша
        session.evict(user1);           // Удалить один объект
        session.clear();                // Очистить весь кэш
        
        // Третий GET — опять из БД
        User user3 = session.get(User.class, 1L);
        // SQL: SELECT * FROM users WHERE id = 1
        
        session.close();
    }
}

Характеристики L1 Cache:

  • Область видимости: Session (уничтожается при close())
  • Включён: Всегда, не конфигурируется
  • Изоляция: Данные изолированы между сессиями
  • Тип: Identity Map (один объект per identifier)

2. Second-Level Cache (Application Cache)

Кэш на уровне SessionFactory — разделяется между сессиями:

// Конфигурация Hibernate для L2 кэша
@Configuration
public class HibernateConfig {
    @Bean
    public FactoryBean<SessionFactory> sessionFactory(DataSource dataSource) {
        LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        
        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty(
            "hibernate.cache.use_second_level_cache", "true"
        );
        hibernateProperties.setProperty(
            "hibernate.cache.region.factory_class",
            "org.hibernate.cache.jcache.JCacheRegionFactory"  // Ehcache, Infinispan
        );
        hibernateProperties.setProperty(
            "hibernate.cache.use_query_cache", "true"
        );
        
        factoryBean.setHibernateProperties(hibernateProperties);
        return factoryBean;
    }
}

// Аннотация @Cacheable
@Entity
@Cacheable  // Кэшируется в L2
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)  // Стратегия
@Table(name = "users")
public class User {
    @Id
    private Long id;
    private String name;
    private String email;
    
    // Отношения также могут быть кэшированы
    @OneToMany(mappedBy = "user")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<Post> posts = new HashSet<>();
}

Пример использования:

public class L2CacheExample {
    public static void main(String[] args) {
        // Session 1
        Session session1 = sessionFactory.openSession();
        User user1 = session1.get(User.class, 1L);
        // SQL: SELECT * FROM users WHERE id = 1
        // Добавляется в L1 и L2 кэш
        session1.close();
        
        // Session 2
        Session session2 = sessionFactory.openSession();
        User user2 = session2.get(User.class, 1L);
        // SQL: НЕ выполняется! Берёт из L2 кэша
        // Добавляется в L1 кэш session2
        
        // user1.id == user2.id, но user1 != user2 (разные объекты)
        assert user1.getId().equals(user2.getId());
        assert user1 != user2;
        
        session2.close();
    }
}

Стратегии кэширования (CacheConcurrencyStrategy)

@Entity
@Cacheable
public class Product {
    @Id
    private Long id;
    private String name;
}

// 1. READ_ONLY — только чтение (никогда не изменяется)
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Config {
    // Лучшая производительность
    // Использование: справочники, константы
}

// 2. READ_WRITE — чтение и запись (может изменяться)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Category {
    // Обычный сценарий
    // Медленнее чем READ_ONLY
}

// 3. NONSTRICT_READ_WRITE — нестрогая консистентность
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class StatisticsData {
    // Может быть несоответствие между кэшем и БД
    // Быстрее READ_WRITE
}

// 4. TRANSACTIONAL — распределённый кэш (JTA)
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
public class BankAccount {
    // Для очень критичных данных
    // Requires JTA и специальный кэш провайдер
}

3. Query Cache

Кэширование результатов запросов:

@Repository
public class UserRepository {
    @PersistenceContext
    private EntityManager em;
    
    // Кэширование результатов query
    public List<User> getActiveUsers() {
        return em.createQuery(
            "SELECT u FROM User u WHERE u.active = true",
            User.class
        )
        .setHint("org.hibernate.cacheable", true)  // Включить кэш
        .setHint("org.hibernate.cacheRegion", "active-users")  // Регион
        .getResultList();
    }
    
    // Или через Hibernate API
    public List<User> getAdminUsers(Session session) {
        Query<User> query = session.createQuery(
            "SELECT u FROM User u WHERE u.role = 'ADMIN'",
            User.class
        );
        query.setCacheable(true);
        query.setCacheRegion("admin-users");
        return query.list();
    }
}

Важно: Query Cache кэширует только ID'ы, сами объекты берутся из L2 кэша!

public class QueryCacheExample {
    public static void main(String[] args) {
        Session session = sessionFactory.openSession();
        
        // Первый запрос
        Query<User> query = session.createQuery(
            "FROM User WHERE active = true", User.class
        );
        query.setCacheable(true);
        List<User> users = query.list();
        // SQL: SELECT id, name, email FROM users WHERE active = true
        // Результат: List<ID> кэшируется
        //           Объекты User кэшируются в L2
        
        // Второй запрос
        query = session.createQuery(
            "FROM User WHERE active = true", User.class
        );
        query.setCacheable(true);
        users = query.list();
        // SQL: НЕ выполняется для этого запроса
        // Берёт ID'ы из Query Cache
        // Берёт объекты из L2 Cache (или создаёт новые, если вышли из L2)
    }
}

Практический пример: Все три уровня

@Service
public class UserService {
    @Autowired
    private UserRepository repository;
    
    public void demonstrateAllCacheLevels() {
        // === Session 1 ===
        // Запросим пользователя
        User user = repository.findById(1L).orElse(null);
        // SQL: SELECT * FROM users WHERE id = 1
        // В L1 кэш (Session 1) и L2 кэш (SessionFactory)
        
        // Повторный запрос в той же сессии
        user = repository.findById(1L).orElse(null);
        // SQL: НЕ выполняется (L1 кэш)
        
        // === Session 2 ===
        user = repository.findById(1L).orElse(null);
        // SQL: НЕ выполняется (L2 кэш)
        
        // === Запрос всех активных пользователей ===
        List<User> activeUsers = repository.findAllActive();
        // SQL: SELECT id FROM users WHERE active = true (Query Cache)
        // SQL: SELECT * FROM users WHERE id = ? (для каждого ID, если не в L2)
        
        // Повторный запрос
        activeUsers = repository.findAllActive();
        // SQL для Query: НЕ выполняется (Query Cache)
        // SQL для объектов: НЕ выполняется (L2 Cache)
    }
}

Инвалидирование кэша

@Service
@Transactional
public class UserManagementService {
    @Autowired
    private UserRepository repository;
    
    public void updateUser(Long id, String newName) {
        User user = repository.findById(id).orElse(null);
        user.setName(newName);
        repository.save(user);
        // Hibernate автоматически инвалидирует кэш
    }
    
    // Ручная очистка кэша
    @Autowired
    private SessionFactory sessionFactory;
    
    public void clearCache() {
        // Очистить весь L2 кэш
        sessionFactory.getCache().evictAllRegions();
        
        // Очистить конкретный регион
        sessionFactory.getCache().evictRegion("User");
        
        // Очистить конкретный объект из L2
        sessionFactory.getCache().evictEntity(User.class, 1L);
    }
}

Провайдеры кэша

public class CacheProviders {
    // 1. Ehcache (self-contained, не требует доп. сервиса)
    // Конфигурация: hibernate.cache.region.factory_class =
    // org.hibernate.cache.EhCacheRegionFactory
    
    // 2. Infinispan (distributed cache, для кластеров)
    // Конфигурация: InfinispanRegionFactory
    
    // 3. Hazelcast (distributed, embedded)
    // Конфигурация: HazelcastRegionFactory
    
    // 4. Redis (external, высокопроизводительный)
    // Требует дополнительной конфигурации
}

Best Practices

public class CachingBestPractices {
    // 1. ✅ Используй L1 кэш для часто читаемых данных
    // 2. ✅ Включай L2 кэш для справочников и константных данных
    // 3. ✅ Используй READ_ONLY для неизменяемых данных
    // 4. ✅ Будь осторожен с Query Cache (может быть дорогостоящим)
    // 5. ❌ Не забывай о консистентности кэша после обновлений
    // 6. ✅ Мониторь эффективность кэша (hit rate)
    // 7. ✅ Используй TTL для кэша (время жизни)
    // 8. ❌ Избегай кэширования часто меняющихся данных
}

Вывод

Кэш в Hibernate — это трёхуровневая система:

  • L1 (Session Cache) — всегда включён, изолирован на сессию
  • L2 (Application Cache) — опционально, разделяется между сессиями
  • Query Cache — кэширует результаты запросов (ID'ы)

Правильное использование кэша может в разы уменьшить нагрузку на БД, но требует понимания когда и как его использовать.

Что такое кэш в Hibernate? | PrepBro