Что такое состояния transient, persistent, detached, removed
Четыре термина описывают статус взаимодействия Java-объекта с механизмом ORM (Hibernate/JPA) в конкретный момент времени. Правильное понимание этих состояний критично для предот...
Обзор
Четыре термина описывают статус взаимодействия Java-объекта с механизмом ORM (Hibernate/JPA) в конкретный момент времени. Правильное понимание этих состояний критично для предотвращения утечек памяти, некорректных обновлений данных и ошибок выполнения.
🟢 Junior Level
4 состояния Entity
| Состояние | В БД? | Отслеживается Hibernate? |
|---|---|---|
| Transient | ❌ Нет | ❌ Нет |
| Persistent | ✅ Да | ✅ Да |
| Detached | ✅ Да | ❌ Нет |
| Removed | ✅ (помечен на удаление) | ✅ (до flush) |
Пример каждого состояния
// 1. Transient — новый объект, не в БД
User user = new User();
user.setName("John");
// user НЕ в БД, Hibernate НЕ следит за ним
// 2. Persistent — сохранён и управляется Hibernate
entityManager.persist(user);
// user в БД (INSERT выполнен или запланирован)
// Hibernate следит за изменениями (dirty checking)
// 3. Detached — запись в БД есть, но Hibernate не следит
entityManager.close();
// user всё ещё существует в памяти
// user всё ещё существует в БД
// Но Hibernate больше не отслеживает изменения
// 4. Removed — помечен на удаление
User user2 = entityManager.find(User.class, 1L);
entityManager.remove(user2);
// user2 помечен на удаление
// При flush/commit будет выполнен DELETE
Как проверить состояние
// Проверить является ли entity управляемым
boolean isManaged = entityManager.contains(user);
// Проверить по ID
// Это работает при GenerationType.AUTO/IDENTITY.
// При Assigned generation ID может быть установлен вручную.
if (user.getId() == null) {
// скорее всего Transient
} else if (entityManager.contains(user)) {
// Persistent
} else {
// Detached (есть ID, но не managed)
}
🟡 Middle Level
Когда НЕ использовать dirty checking
Dirty checking удобен для единичных обновлений. При массовой обработке (1000+ сущностей) overhead растёт — используйте native SQL queries или StatelessSession.
Операции перехода между состояниями
persist() — Transient → Persistent
merge() — Detached → Persistent (возвращает новую managed копию)
remove() — Persistent → Removed
refresh() — reload из БД (обновляет Persistent)
detach() — Persistent → Detached (evict() в Hibernate)
clear() — все Persistent → Detached
Детальное поведение каждой операции
persist()
User user = new User();
user.setName("John");
entityManager.persist(user);
// user стал Persistent
// INSERT будет выполнен при flush/commit
user.setName("Jane"); // dirty checking — будет UPDATE
merge()
// merge — самая сложная для понимания операция
User detached = getDetachedUser(); // где-то получен detached объект
User managed = entityManager.merge(detached);
// Что происходит внутри:
// 1. Hibernate ищет entity с таким ID в текущей сессии
// 2. Если не находит — загружает из БД
// 3. Копирует состояние из detached в managed
// 4. Возвращает managed объект
// 5. detached остаётся detached!
detached.setName("Changed"); // НЕ повлияет на БД
managed.setName("Changed"); // повлияет (dirty checking)
SELECT нужен чтобы Hibernate проверил: (1) существует ли запись в БД, (2) загрузил текущее состояние для dirty checking. Без SELECT Hibernate не знает что обновлять.
detach() / evict()
User user = entityManager.find(User.class, 1L); // Persistent
entityManager.detach(user); // user → Detached
// user.getId() всё ещё = 1
// Но изменения больше не отслеживаются
// Hibernate-специфичный аналог:
Session session = entityManager.unwrap(Session.class);
session.evict(user);
refresh()
User user = entityManager.find(User.class, 1L);
// Другая транзакция изменила user в БД
entityManager.refresh(user); // перезагрузить из БД
// Теперь user имеет актуальные значения
Типичные ошибки
// ❌ Detached entity passed to persist
User user = new User();
user.setId(1L); // ID установлен вручную
entityManager.persist(user); // ❌ EntityExistsException
// ✅ Правильно
User merged = entityManager.merge(user);
// ❌ Игнорирование return value merge()
entityManager.merge(detached); // результат потерян!
detached.setName("Changed"); // НЕ сохранится
// ✅ Правильно
User managed = entityManager.merge(detached);
managed.setName("Changed"); // сохранится
// ❌ merge() для persistent
User user = entityManager.find(User.class, 1L); // уже persistent
entityManager.merge(user); // лишний SELECT!
🔴 Senior Level
Внутренняя реализация
Структура Persistence Context:
EntityManagerImpl {
persistenceContext: StatefulPersistenceContext {
entitiesByKey: Map<EntityKey, Object>,
entityEntries: Map<Object, EntityEntry>,
}
}
EntityEntry хранит:
- loadedState — состояние при загрузке (snapshot)
- status — MANAGED, DELETED, READ_ONLY, etc.
- id — identifier
- version — version для optimistic locking
- lazyPropertiesAreUninitialized — флаг lazy загрузки
Identity Guarantee
В рамках одной сессии Hibernate гарантирует:
- Для одного ID в БД существует ровно один объект в памяти
- entityManager.find(User.class, 1L) всегда вернёт тот же объект
User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);
assert user1 == user2; // true (identity)
Это важно для:
-_dirty checking (один объект отслеживается один раз)
- консистентности данных в рамках транзакции
Senior-инсайт: Магия Merge
Когда вы вызываете em.merge(detachedObject):
1. Проверка: есть ли entity с таким ID в persistence context?
→ Да: копируем состояние из detached в managed
→ Нет: загружаем из БД (SELECT)
2. Если в БД нет — создаём новый managed (INSERT при flush)
3. Копируем все поля из detachedObject в managed
4. Возвращаем managed объект
5. detachedObject остаётся detached!
Все дальнейшие действия — с returned object!
Важно: merge() может вызвать SELECT перед INSERT/UPDATE
Оптимизация для больших данных
@Transactional
public void batchImport(List<User> users) {
int batchSize = 50;
for (int i = 0; i < users.size(); i++) {
entityManager.persist(users.get(i));
if (i % batchSize == 0 && i > 0) {
entityManager.flush(); // сбросить в БД
entityManager.clear(); // очистить persistence context
}
}
}
Best Practices
✅ Понимайте состояния сущностей
✅ persist() для новых
✅ merge() для detached (используйте return value!)
✅ Избегайте долгого хранения detached объектов
✅ Используйте entityManager.contains() для проверки
✅ clear() для больших пакетных операций
❌ persist() с установленным ID
❌ merge() для persistent (лишний SELECT)
❌ Игнорирование return value merge()
❌ Доступ к lazy полям detached объектов
❌ Передача detached объектов между потоками
Сравнительная таблица
| Состояние | Есть в БД? | В Persistence Context? | Dirty Checking? |
|---|---|---|---|
| Transient | Нет | Нет | Нет |
| Persistent | Да | Да | Да |
| Detached | Да | Нет | Нет |
| Removed | Ещё да | Да (до flush) | Нет |
🎯 Шпаргалка для интервью
Обязательно знать:
- Transient: новый объект, не в БД, Hibernate не следит
- Persistent: в БД, в persistence context, dirty checking работает
- Detached: в БД, но НЕ в persistence context, изменения не отслеживаются
- Removed: помечен на удаление, будет DELETE при flush
- persist() — Transient → Persistent, merge() — Detached → Persistent (возвращает копию!)
- entityManager.contains() проверяет является ли сущность managed
- Identity guarantee: в одной сессии один ID = один объект
Частые уточняющие вопросы:
- Как проверить состояние? ID null → Transient, contains() true → Persistent, contains() false + ID есть → Detached
- Почему merge() возвращает новый объект? Оригинальный detached остаётся detached, Hibernate создаёт managed копию
- Что такое Identity Guarantee? В рамках одной сессии Hibernate гарантирует один ID = один объект в памяти
- Когда detach() нужен? Для освобождения памяти, при batch-операциях, при обработке больших данных
Красные флаги (НЕ говорить):
- «persist() для объекта с установленным ID» — EntityExistsException
- «merge() для persistent — нормально» — лишний SELECT
- «Detached объекты можно изменять напрямую» — нужны merge()
- «Передаю detached объекты между потоками» — не thread-safe
Связанные темы:
- [[7. Опишите жизненный цикл Entity в Hibernate]]
- [[14. В чём разница между persist() и merge()]]
- [[12. Что такое dirty checking в Hibernate]]
- [[15. Что делает метод refresh()]]