Опишіть життєвий цикл Entity в Hibernate
Кожна сутність в Hibernate проходить через певні стани (життєвий цикл) в процесі взаємодії з базою даних. Розуміння життєвого циклу критично важливе для правильного використання...
Огляд
Кожна сутність в Hibernate проходить через певні стани (життєвий цикл) в процесі взаємодії з базою даних. Розуміння життєвого циклу критично важливе для правильного використання Hibernate та запобігання помилкам.
Навіщо це знати: щоб розуміти який метод викликати (persist чи merge), чому зміни зберігаються самі (dirty checking), чому виникає «detached entity passed to persist».
🟢 Junior Level
4 стани Entity
Сутність в Hibernate може перебувати в одному з чотирьох станів:
- Transient (Новий / Тимчасовий) — створений через
new, ще не в БД - Persistent (Керований) — збережений в БД, відстежується Hibernate
- Detached (Від’єднаний) — був в БД, але сесія закрита
- Removed (Видалений) — помічений на видалення, буде видалений при flush
new User() ──persist()──→ Managed ──close()──→ Detached
│
remove()
↓
Removed
Приклад
// 1. Transient — новий об'єкт, не в БД
User user = new User();
user.setName("John");
// 2. Persistent — збережений, відстежується
entityManager.persist(user);
// Тепер user в БД і під керуванням Hibernate
// 3. Detached — сесія закрита
entityManager.close();
// user все ще існує в пам'яті, але Hibernate більше не стежить
// 4. Removed — помічений на видалення
entityManager.remove(user);
// При flush/commit буде виконаний DELETE
🟡 Middle Level
persist() vs merge()
| Операція | Transient | Detached | Persistent |
|---|---|---|---|
| persist() | INSERT | ПОМИЛКА | ПОМИЛКА |
| merge() | SELECT+INSERT | SELECT+UPDATE | SELECT (марно) |
Переходи між станами
Transient → Persistent: persist(), merge()
Persistent → Detached: close(), clear(), detach()/evict()
Detached → Persistent: merge(), update()
Persistent → Removed: remove()
Transient → Detached: (неможливо напряму)
Детальний опис операцій
// persist() — для нових сутностей (INSERT)
User newUser = new User();
entityManager.persist(newUser);
// newUser тепер Persistent
// merge() — для detached сутностей (UPDATE)
User detached = getDetachedUser();
User managed = entityManager.merge(detached);
// managed — нова керована копія
// detached залишається detached!
// remove() — для видалення (DELETE)
entityManager.remove(managedUser);
// При flush буде виконаний DELETE
// refresh() — перезавантажити з БД
entityManager.refresh(user);
// Оновлює дані з БД, перезаписуючи зміни в пам'яті
// detach()/evict() — від'єднати. evict() — Hibernate-specific (Session). detach() — JPA-стандарт (EntityManager). Використовуйте detach() для переносимості.
entityManager.detach(user);
// user стає Detached
// clear() — від'єднати всі
entityManager.clear();
// Всі сутності стають Detached
Dirty Checking
Dirty checking — Hibernate автоматично відстежує зміни об’єктів. Ви просто змінюєте поле — Hibernate сам робить UPDATE.
@Transactional
public void updateUser(Long id, String name) {
User user = entityManager.find(User.class, id); // Persistent
user.setName(name);
// НЕ ПОТРІБЕН em.merge() чи em.save()!
// Hibernate автоматично виявить зміну і зробить UPDATE
}
Типові помилки
// ❌ Detached entity passed to persist
User user = new User();
user.setId(1L); // ID вже встановлений
entityManager.persist(user); // ❌ EntityExistsException або помилка
// ✅ Правильно: merge() для detached
User merged = entityManager.merge(user);
// ❌ merge() для нової сутності (зайвий SELECT)
User newUser = new User();
entityManager.merge(newUser); // Спочатку SELECT, потім INSERT
// ✅ Правильно: persist() для нових
entityManager.persist(newUser); // Одразу INSERT
// ❌ merge() для persistent (марно)
User user = entityManager.find(User.class, 1L); // Persistent
User merged = entityManager.merge(user); // Зайвий SELECT
🔴 Senior Level
Внутрішня реалізація
Persistence Context — контейнер для всіх managed сутностей в рамках EntityManager. EntityEntry — запис всередині Persistence Context, зберігає поточний стан та snapshot. Snapshot — копія стану об’єкта на момент завантаження, використовується для dirty checking.
Persistence Context (StatefulPersistenceContext):
EntityManagerImpl {
persistenceContext: StatefulPersistenceContext {
entitiesByKey: Map<EntityKey, Object>, // cache by ID
entityEntries: Map<Object, EntityEntry>, // state tracking
}
}
EntityEntry зберігає:
- snapshot — стан при завантаженні (для dirty checking)
- status — поточний стан (MANAGED, DELETED, тощо)
- loadedState — стан при завантаженні з БД
Механізм dirty checking
1. При завантаженні: Hibernate зберігає snapshot entity
2. При зміні: змінюється поле в об'єкті
3. При flush: Hibernate порівнює snapshot з поточним станом
4. Якщо різні → генерує UPDATE SQL
5. Оновлює snapshot
Алгоритм:
- O(N) де N = число entities в persistence context
- Для великих контекстів може бути повільно
- Рішення: entityManager.clear() періодично
Управління пам’яттю при великих операціях
@Transactional
public void batchProcess(List<User> users) {
for (int i = 0; i < users.size(); i++) {
entityManager.merge(users.get(i));
// Періодичне очищення persistence context
if (i % 50 == 0) {
entityManager.flush(); // скинути в БД
entityManager.clear(); // очистити кеш
}
}
}
Після clear() всі раніше завантажені/створені об’єкти стають detached. Якщо цикл продовжується — наступні ітерації працюють з новими об’єктами, старі втрачаються (і при зверненні до них знадобиться merge()).
Best Practices
✅ persist() для нових сутностей
✅ merge() для detached сутностей
✅ remove() для видалення
✅ Dirty checking для оновлень (не потрібен merge)
✅ entityManager.clear() для пакетних операцій
✅ flush + clear кожні 50-100 entities
❌ persist() для detached (помилка)
❌ merge() для нових (зайвий SELECT)
❌ merge() для persistent (зайвий SELECT)
❌ Ігнорування станів сутностей
❌ Завантаження великої кількості entities без clear
🎯 Шпаргалка для співбесіди
Обов’язково знати:
- 4 стани: Transient (new), Persistent (managed), Detached (в БД, але не tracked), Removed (помічений на видалення)
- persist() — для нових (INSERT), merge() — для detached (UPDATE/INSERT)
- Dirty checking: Hibernate автоматично відстежує зміни managed-сутностей
- Persistence Context — контейнер для всіх managed сутностей, зберігає snapshot для dirty checking
- merge() повертає НОВИЙ managed об’єкт, оригінал залишається detached
- entityManager.clear() очищує persistence context, всі сутності стають detached
Часті уточнюючі запитання:
- Чому persist() для нових краще merge()? persist() = одразу INSERT, merge() = спочатку SELECT (перевірка)
- Що таке snapshot? Копія стану при завантаженні, використовується для dirty checking при flush
- Як обробити 10k+ сутностей? persist/merge + flush + clear кожні 50-100 entities
- Чому merge() повертає новий об’єкт? Тому що оригінал може бути detached, Hibernate створює managed копію
Червоні прапорці (НЕ говорити):
- «Використовую merge() для нових сутностей» — зайвий SELECT
- «Ігнорую return value merge()» — зміни не збережуться
- «persist() для detached з ID» — EntityExistsException
- «Не чищу persistence context при batch» — OutOfMemoryError
Пов’язані теми:
- [[8. Що таке стани transient, persistent, detached, removed]]
- [[12. Що таке dirty checking в Hibernate]]
- [[13. Як працює механізм flush в Hibernate]]
- [[14. У чому різниця між persist() і merge()]]
- [[9. Що таке кеш першого рівня в Hibernate]]