Що таке кеш першого рівня в Hibernate
Кеш першого рівня (L1 cache) — вбудований кеш на рівні EntityManager/Session. Він є фундаментальною частиною Hibernate і забезпечує ідентичність сутностей, dirty checking та опт...
Огляд
Кеш першого рівня (L1 cache) — вбудований кеш на рівні EntityManager/Session. Він є фундаментальною частиною Hibernate і забезпечує ідентичність сутностей, dirty checking та оптимізацію запитів.
Навіщо: L1 cache вирішує три проблеми: (1) Identity guarantee — один ID = один об’єкт в пам’яті; (2) Dirty checking — потрібно зберігати snapshot для порівняння; (3) Оптимізація — повторний find() не робить SELECT.
🟢 Junior Level
Що таке L1 cache
Кеш першого рівня — це кеш всередині EntityManager (сесії). Всі завантажені сутності зберігаються в ньому автоматично.
// Перший запит — SELECT з БД
User user1 = entityManager.find(User.class, 1L);
// SQL: SELECT * FROM users WHERE id = 1
// Другий запит — з кешу, БЕЗ SELECT!
User user2 = entityManager.find(User.class, 1L);
// SQL: (немає запиту!)
assert user1 == user2; // true — один і той самий об'єкт в пам'яті
Основні властивості
- ✅ Увімкнений за замовчуванням, не можна вимкнути
- 🔄 Живе в межах однієї сесії/транзакції
- 🗑️ Автоматично очищується при закритті EntityManager
- 🔒 Забезпечує identity guarantee (один ID = один об’єкт). Якщо в одній транзакції двічі завантажити User(1), Hibernate поверне ОДИН І ТОЙ САМ об’єкт в пам’яті (user1 == user2). Це потрібно щоб dirty checking працював коректно — інакше зміни одного об’єкта могли б конфліктувати з іншим.
Коли спрацьовує
// find() — перевіряє кеш перед запитом до БД
User user1 = entityManager.find(User.class, 1L); // SELECT
User user2 = entityManager.find(User.class, 1L); // з кешу
// persist() — додає в кеш
User newUser = new User();
entityManager.persist(newUser); // в кеші
// merge() — додає оновлену копію в кеш
User managed = entityManager.merge(detachedUser); // в кеші
🟡 Middle Level
Коли L1 cache шкідливий
- Завантаження 10k+ сутностей в одній транзакції — OutOfMemoryError
- Streaming великих даних — використовуйте ScrollableResults
- Пакетні операції — використовуйте StatelessSession
Dirty checking через L1 cache
// Hibernate зберігає snapshot при завантаженні
User user = entityManager.find(User.class, 1L); // snapshot створено
// Змінюємо поле
user.setName("New Name");
// При commit/flush — Hibernate порівнює snapshot з поточним станом
// Якщо різні — автоматично робить UPDATE
// Не потрібен явний entityManager.merge()!
Управління кешем
// Очистити весь кеш
entityManager.clear();
// Всі сутності стають detached
// Видалити конкретну сутність з кешу
entityManager.detach(user);
// user стає detached, решта залишаються
// Перезавантажити з БД (оновити кеш)
entityManager.refresh(user);
// SELECT з БД, оновлення snapshot
// Перевірити наявність в кеші
boolean isManaged = entityManager.contains(user);
Проблема великого L1 cache
// ❌ Завантажили 100k entities → OutOfMemoryError!
for (Long id : allIds) {
entityManager.find(User.class, id); // всі в кеші
}
// ✅ Рішення: періодичне очищення
for (int i = 0; i < allIds.size(); i++) {
entityManager.find(User.class, allIds.get(i));
if (i % 100 == 0) {
entityManager.flush(); // скинути в БД
entityManager.clear(); // очистити кеш
}
}
🔴 Senior Level
L1 cache vs ручний Map
На відміну від ручного Map, L1 cache: (1) автоматично заповнюється при будь-якому запиті; (2) забезпечує identity guarantee; (3) зберігає snapshot для dirty checking; (4) автоматично очищується при закритті EntityManager.
Внутрішня реалізація
StatefulPersistenceContext {
entitiesByKey: Map<EntityKey, Object>
entityEntries: Map<Object, EntityEntry>
}
EntityEntry містить:
- loadedState — snapshot при завантаженні
- status — MANAGED, DELETED, READ_ONLY
- id — identifier
- version — для optimistic locking
Read-only hint для оптимізації
List<User> users = entityManager.createQuery("FROM User", User.class)
.setHint("org.hibernate.readOnly", true)
.getResultList();
// Переваги:
// 1. Не створюється snapshot (економія пам'яті)
// 2. Dirty checking не виконується
// 3. Менше overhead на flush
Best Practices
✅ L1 cache працює за замовчуванням
✅ entityManager.clear() для пакетних операцій
✅ read-only hint для запитів без змін
✅ flush + clear кожні 50-100 entities в batch
❌ Завантаження великої кількості entities без clear
❌ Використання L1 cache як кеш між запитами
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- L1 cache — вбудований кеш EntityManager, увімкнений за замовчуванням, не можна вимкнути
- Вирішує 3 проблеми: identity guarantee, dirty checking (snapshot), оптимізація запитів
- find() перевіряє кеш перед запитом до БД — повторний find() без SQL
- Живе в межах однієї транзакції, очищується при закритті EntityManager
- При завантаженні 10k+ сутностей — OutOfMemoryError, потрібен clear()
- Read-only hint економить пам’ять (не створює snapshot)
Часті уточнюючі запитання:
- Чи можна вимкнути L1 cache? Ні, це фундаментальна частина Hibernate
- Як уникнути OOM при batch? flush() + clear() кожні 50-100 entities
- L1 cache vs ручний Map? L1 cache автоматично заповнюється, забезпечує identity guarantee і зберігає snapshot
Червоні прапорці (НЕ говорити):
- «Використовую L1 cache як кеш між запитами» — він живе тільки в рамках однієї сесії
- «Вимикаю L1 cache для продуктивності» — не можна вимкнути
- «Завантажую 100k entities без clear» — OutOfMemoryError
Пов’язані теми:
- [[10. Що таке кеш другого рівня і коли його використовувати]]
- [[11. Як налаштувати кеш другого рівня]]
- [[12. Що таке dirty checking в Hibernate]]
- [[13. Як працює механізм flush в Hibernate]]