← Назад к вопросам
Что такое кэш в 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'ы)
Правильное использование кэша может в разы уменьшить нагрузку на БД, но требует понимания когда и как его использовать.