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

Как работает кэширование в Hibernate?

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

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

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

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

Как работает кэширование в Hibernate

Кэширование в Hibernate - это мощный механизм оптимизации производительности, который снижает количество запросов к базе данных. Существует несколько уровней кэширования.

1. First-Level Cache (L1 Cache) - уровень Session

Это встроенный кэш Hibernate, работает на уровне Session:

// Сессия - это транзакция с БД
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

// Первый запрос к БД
User user1 = session.get(User.class, 1L);
System.out.println(user1.getName()); // SQL запрос выполнен

// Второй запрос - берет из кэша Session (L1)
User user2 = session.get(User.class, 1L);
System.out.println(user2.getName()); // SQL НЕ выполнен! Из кэша

// Это один и тот же объект в памяти
System.out.println(user1 == user2); // true

tx.commit();
session.close(); // L1 кэш очищается

2. Second-Level Cache (L2 Cache)

Глобальный кэш для всей SessionFactory, может быть общим между сессиями:

// Конфигурация в persistence.xml
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" 
          value="org.hibernate.cache.jcache.JCacheRegionFactory"/>

// Или в Java конфигурации
@Configuration
public class HibernateConfig {
    
    @Bean
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
        Properties properties = new Properties();
        properties.setProperty(
            "hibernate.cache.use_second_level_cache", "true"
        );
        properties.setProperty(
            "hibernate.cache.region.factory_class",
            "org.hibernate.cache.jcache.JCacheRegionFactory"
        );
        factory.setHibernateProperties(properties);
        return factory;
    }
}

Маркировка сущности для кэширования:

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Table(name = "users")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    @Id
    private Long id;
    private String name;
    private String email;
    
    // getters, setters...
}

3. Использование L2 Cache

SessionFactory sessionFactory = // инициализация

// Первая сессия
Session session1 = sessionFactory.openSession();
User user1 = session1.get(User.class, 1L);
System.out.println(user1.getName()); // SQL запрос
session1.close(); // L1 кэш очищается, но L2 остается

// Вторая сессия
Session session2 = sessionFactory.openSession();
User user2 = session2.get(User.class, 1L);
System.out.println(user2.getName()); // SQL НЕ выполнен! Из L2 кэша
session2.close();

// Это разные объекты, но содержат одинаковые данные
System.out.println(user1 == user2); // false
System.out.println(user1.getId().equals(user2.getId())); // true

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

// READ_ONLY - только для чтения, не обновляется
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Role {
    // Никогда не меняется
}

// READ_WRITE - читаемое и записываемое кэширование
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    // Может обновляться, Hibernate управляет инвалидацией
}

// NONSTRICT_READ_WRITE - менее строгое, лучшая производительность
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Product {
    // Может быть временная несогласованность, но быстро
}

// TRANSACTIONAL - для JTA транзакций
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
public class Order {
    // Требует JTA
}

5. Кэширование коллекций и связей

@Entity
@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
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Order {
    @Id
    private Long id;
    
    @ManyToOne
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private User user;
}

6. Query Cache - кэширование результатов запросов

// Конфигурация
<property name="hibernate.cache.use_query_cache" value="true"/>

// Использование
Session session = sessionFactory.openSession();

Query query = session.createQuery("FROM User WHERE status = :status");
query.setParameter("status", "active");
query.setCacheable(true);  // Кэшируем результаты
query.setCacheRegion("activeUsers"); // Опционально указываем регион

List<User> users = query.list(); // Первый запрос к БД

List<User> users2 = query.list(); // Из кэша! Без SQL

7. Инвалидация кэша

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

User user = session.get(User.class, 1L); // Загружается в кэш
user.setName("New Name");
session.update(user); // Hibernate автоматически инвалидирует кэш

tx.commit();
session.close();

// Вторая сессия получит обновленные данные
Session session2 = sessionFactory.openSession();
User updatedUser = session2.get(User.class, 1L); // Новый SQL запрос
session2.close();

8. Ручная управление кэшем

SessionFactory sessionFactory = // инициализация

// Очистить L2 кэш для конкретной сущности
sessionFactory.getCache().evictEntity(User.class, 1L);

// Очистить весь L2 кэш для сущности
sessionFactory.getCache().evictEntityRegion(User.class);

// Очистить все коллекции
sessionFactory.getCache().evictCollectionRegion(User.class, "orders");

// Очистить весь L2 кэш
sessionFactory.getCache().evictAllRegions();

9. Пример с Spring Data JPA

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    @Id
    @GeneratedValue
    private Long id;
    private String email;
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    @Cacheable("users")
    Optional<User> findByEmail(String email);
}

@Service
public class UserService {
    @Autowired
    private UserRepository repository;
    
    @Cacheable("user-cache")
    public User getUserById(Long id) {
        return repository.findById(id).orElse(null);
    }
    
    @CacheEvict("user-cache")
    public void updateUser(User user) {
        repository.save(user);
    }
}

10. Производительность и проблемы

// ❌ Проблема: много объектов в памяти
for (int i = 0; i < 1000000; i++) {
    User user = session.get(User.class, i);
    // Все объекты в L1 кэше!
    // Утечка памяти
}

// ✅ Решение: периодически очищайте L1 кэш
for (int i = 0; i < 1000000; i++) {
    User user = session.get(User.class, i);
    if (i % 100 == 0) {
        session.clear(); // Очистить L1 кэш
    }
}

// ❌ Проблема: устаревший кэш при изменениях вне Hibernate
update users set name = 'Updated' where id = 1; // Прямо в БД
User user = session.get(User.class, 1L); // Вернет старые данные из кэша!

// ✅ Решение: инвалидировать кэш
sessionFactory.getCache().evictEntity(User.class, 1L);
User user = session.get(User.class, 1L); // Новый запрос к БД

Иерархия кэширования

Session.get() → L1 Cache (Session) → L2 Cache → База данных

Вывод: кэширование в Hibernate работает на двух уровнях - Session (L1, автоматический) и SessionFactory (L2, требует конфигурации). Правильное использование кэширования значительно улучшает производительность, но требует понимания особенностей каждого уровня.