← Назад к вопросам
Какая область видимости Cache первого уровня?
2.0 Middle🔥 101 комментариев
#ORM и Hibernate#Кэширование и NoSQL
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Cache первого уровня (L1 Cache): область видимости и механизм в Hibernate
Кеш первого уровня — это одна из ключевых особенностей Hibernate ORM. Рассмотрю его устройство, область видимости и практическое применение.
Область видимости L1 Cache
Ответ: L1 Cache привязан к SESSION (Сессия Hibernate)
Архитектура кэширования:
┌─────────────────────────────────────────────────────┐
│ L1 Cache (First-Level Cache) - SESSION SCOPE │
│ Область видимости: внутри одной сессии Hibernate │
├─────────────────────────────────────────────────────┤
│ - Не кэшируется между сессиями │
│ - Очищается при close() сессии │
│ - Проверяется перед SELECT запросами │
│ - Управляется Hibernate автоматически │
│ - Нельзя отключить │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ L2 Cache (Second-Level Cache) - APPLICATION SCOPE │
│ Область видимости: на весь Application │
│ (Redis, Memcached, EhCache и т.д.) │
└─────────────────────────────────────────────────────┘
Как работает L1 Cache
Пример 1: Базовый сценарий
public class FirstLevelCacheExample {
public static void main(String[] args) {
SessionFactory sessionFactory = new Configuration()
.configure("hibernate.cfg.xml")
.buildSessionFactory();
// Первая сессия
Session session1 = sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
// Первый SELECT - идёт в БД
User user1 = session1.get(User.class, 1L);
System.out.println("Первый запрос: SELECT * FROM users WHERE id = 1");
// Второй SELECT с тем же id - берётся из L1 Cache
User user2 = session1.get(User.class, 1L);
System.out.println("Второй запрос: НЕТ запроса в БД (данные из cache)");
// user1 == user2 (один и тот же объект в памяти)
System.out.println("user1 == user2: " + (user1 == user2)); // true
tx1.commit();
session1.close(); // L1 Cache очищается
// Вторая сессия - новый L1 Cache
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
// Третий SELECT - опять идёт в БД (другая сессия)
User user3 = session2.get(User.class, 1L);
System.out.println("Третий запрос: SELECT * FROM users WHERE id = 1 (новая сессия)");
// user1 != user3 (разные объекты, но одинаковые данные)
System.out.println("user1 == user3: " + (user1 == user3)); // false
tx2.commit();
session2.close();
}
}
Важные вывод:
- Кэш существует только внутри одной сессии
- При закрытии сессии кэш очищается
- Нельзя поделиться данными между сессиями через L1 Cache
Содержимое L1 Cache
public class CacheContentsExample {
public static void main(String[] args) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// L1 Cache пуст
System.out.println("Количество объектов в cache: " +
session.getStatistics().getEntityCount()); // 0
// Загружаем пользователя
User user = session.get(User.class, 1L);
// L1 Cache: {User#1 -> user object}
System.out.println("Количество объектов в cache: " +
session.getStatistics().getEntityCount()); // 1
// Загружаем заказ
Order order = session.get(Order.class, 100L);
// L1 Cache: {User#1 -> user, Order#100 -> order}
System.out.println("Количество объектов в cache: " +
session.getStatistics().getEntityCount()); // 2
// Модифицируем объект (грязный статус)
user.setEmail("new@example.com");
// Объект остаётся в cache с пометкой "dirty"
tx.commit();
// Сохранённые объекты остаются в L1 Cache
session.close();
// L1 Cache очищается полностью
}
}
Управление L1 Cache
Очистка вручную
public class ManualCacheClearExample {
public static void main(String[] args) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L);
// Объект в L1 Cache
// Очистить весь L1 Cache
session.clear();
// Теперь user НЕ в cache, но объект java остаётся
// Повторный запрос - идёт в БД
User user2 = session.get(User.class, 1L);
System.out.println("user == user2: " + (user == user2)); // false
tx.commit();
// Евicting отдельного объекта
session.evict(user); // Удалить конкретный объект из cache
session.close();
}
}
Refresh объекта из БД
public class RefreshFromDBExample {
public static void main(String[] args) {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L);
System.out.println("Email: " + user.getEmail()); // john@example.com
// Обновляем напрямую в БД (другой процесс)
// UPDATE users SET email = new@example.com WHERE id = 1
// В Hibernate объект остаётся старый
System.out.println("Email в cache: " + user.getEmail()); // john@example.com
// Обновляем объект из БД
session.refresh(user);
System.out.println("Email после refresh: " + user.getEmail()); // new@example.com
tx.commit();
session.close();
}
}
Уровни кэширования в Hibernate
Запрос в Hibernate:
↓
┌──────────────────────┐
│ L1 Cache (Session) │ ← Проверяем первый уровень
└──────────────────────┘
↓
Не найден? → Проверяем L2 Cache
↓
┌──────────────────────┐
│ L2 Cache (AppLevel) │ ← Проверяем второй уровень
└──────────────────────┘
↓
Не найден? → Проверяем Query Cache
↓
┌──────────────────────┐
│ Query Cache │
└──────────────────────┘
↓
Не найден? → SELECT из БД
↓
Добавляем в L1 Cache → Ответ
Spring Data и L1 Cache
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional // Сессия жива во время метода
public void processUser() {
// Транзакция открывает сессию Hibernate
User user1 = userRepository.findById(1L).get();
// SELECT выполнен, user1 в L1 Cache
User user2 = userRepository.findById(1L).get();
// SELECT НЕ выполнен (данные из L1 Cache)
System.out.println(user1 == user2); // true
// Сессия закрывается, L1 Cache очищается
}
// Отдельные транзакции - отдельные сессии
@Transactional
public void firstTransaction() {
User user = userRepository.findById(1L).get(); // SELECT #1
} // Сессия закрывается, cache очищается
@Transactional
public void secondTransaction() {
User user = userRepository.findById(1L).get(); // SELECT #2 (cache другой)
}
}
Проблемы и решения
Проблема 1: Stale Data (устаревшие данные)
public void staledDataExample() {
Session session = sessionFactory.openSession();
User user = session.get(User.class, 1L);
System.out.println(user.getEmail()); // john@example.com
// Другой процесс обновил в БД
// UPDATE users SET email = new@example.com WHERE id = 1
// Hibernate не знает об этом, возвращает старые данные
System.out.println(user.getEmail()); // john@example.com (STALE!)
// Решение: refresh
session.refresh(user);
System.out.println(user.getEmail()); // new@example.com
}
Проблема 2: Memory Leak в длительных сессиях
public void memoryLeakExample() {
Session session = sessionFactory.openSession();
for (int i = 0; i < 100_000; i++) {
User user = session.get(User.class, (long)i);
// L1 Cache растёт: 100k объектов в памяти
// Может привести к OutOfMemoryError
}
session.close();
// Решение: периодически очищать cache
Session session2 = sessionFactory.openSession();
for (int i = 0; i < 100_000; i++) {
User user = session2.get(User.class, (long)i);
if (i % 1000 == 0) {
session2.clear(); // Очистить каждые 1000 объектов
}
}
session2.close();
}
Конфигурация L1 Cache
<!-- hibernate.cfg.xml -->
<hibernate-configuration>
<session-factory>
<!-- L1 Cache нельзя отключить, но можно контролировать поведение -->
<!-- Автоматическое управление сессией -->
<property name="current_session_context_class">thread</property>
<!-- Flush mode (как часто синхронизировать с БД) -->
<property name="flush_before_query">true</property>
<!-- Статистика (для отладки) -->
<property name="generate_statistics">true</property>
</session-factory>
</hibernate-configuration>
Ключевые правила L1 Cache
✅ Помните:
- L1 Cache = Session Scope — привязан к одной сессии
- Обязателен — нельзя отключить, всегда работает
- Автоматический — Hibernate управляет сам
- Идентичность объектов — user1 == user2 в одной сессии
- Очищается — при close() или clear()
- Уменьшает запросы — SELECT один раз за сессию
- Thread-safe — каждый поток имеет свою сессию
Сравнение кэшей
| Параметр | L1 Cache | L2 Cache | Query Cache |
|---|---|---|---|
| Область видимости | Session | Application | Global |
| Управление | Automatic | Manual | Manual |
| Может быть отключен | Нет | Да | Да |
| Тип хранилища | Memory (JVM) | Configurable | Memory/Disk |
| По умолчанию | Включен | Отключен | Отключен |
| Примеры | - | Redis, EhCache | - |
Резюме
L1 Cache имеет область видимости SESSION — это ключевая особенность, которая отличает его от L2 Cache (Application scope). Каждая сессия Hibernate имеет свой собственный L1 Cache, который очищается при закрытии сессии. Это гарантирует корректность и изоляцию данных между параллельными запросами.