В чём разница между persist() и merge()
persist() и merge() — два ключевых метода EntityManager для управления жизненным циклом сущностей. Понимание разницы между ними критически важно для правильного использования Hi...
Обзор
persist() и merge() — два ключевых метода EntityManager для управления жизненным циклом сущностей. Понимание разницы между ними критически важно для правильного использования Hibernate.
🟢 Junior Level
Основное различие
persist() — для новых сущностей (выполняет INSERT).
merge() — для обновления detached сущностей (выполняет UPDATE или INSERT).
// persist — для новых сущностей
User newUser = new User();
newUser.setName("John");
entityManager.persist(newUser); // INSERT
// newUser теперь Persistent
// merge — для detached сущностей
User detached = getDetachedUser(); // где-то получен detached
User managed = entityManager.merge(detached); // UPDATE
// managed — новая managed копия
// detached остаётся detached!
Поведение
| persist() | merge() | |
|---|---|---|
| Для | Transient | Detached |
| SQL | INSERT | UPDATE или INSERT |
| Возвращает | void | Managed entity |
| Исходный объект | Становится managed | Остаётся detached |
Пример persist()
User user = new User();
user.setName("John");
entityManager.persist(user);
// user стал Persistent
// Можно продолжать работу с user
user.setName("Jane"); // dirty checking сработает
Пример merge()
User detached = getDetachedUser();
detached.setName("Changed");
User managed = entityManager.merge(detached);
// detached — всё ещё detached!
// managed — новая managed копия
detached.setName("NotSaved"); // НЕ повлияет на БД
managed.setName("WillBeSaved"); // повлияет (dirty checking)
🟡 Middle Level
Когда merge() реально нужен
merge() типично нужен в веб-приложениях: объект пришёл с фронта (JSON → DTO → entity), он detached — merge копирует его состояние в managed контекст.
persist() детально
User user = new User();
entityManager.persist(user);
// Что происходит:
// 1. Проверяет что entity не managed
// 2. Генерирует ID (если auto)
// 3. Добавляет в persistence context
// 4. Планирует INSERT при flush
// 5. user становится Persistent
// После persist можно работать
user.setName("Updated"); // dirty checking
merge() детально
User detached = getDetachedUser();
User managed = entityManager.merge(detached);
// Что происходит внутри:
// 1. Проверяет есть ли entity с таким ID в persistence context
// 2. Если нет — загружает из БД (SELECT)
// 3. Копирует состояние из detached в managed
// 4. Планирует UPDATE при flush (если есть изменения)
// 5. Возвращает managed entity
// ВАЖНО:
// detached остаётся detached!
// Все дальнейшие операции — с managed!
persist vs merge — когда что использовать
persist() — Transient → Persistent, INSERT
merge() — Detached → Persistent, UPDATE или INSERT
Типичные ошибки
// ❌ merge() для новой сущности — лишний SELECT
User newUser = new User();
entityManager.merge(newUser);
// Сначала SELECT (проверка существования), потом INSERT
// ✅ persist() для новых
entityManager.persist(newUser); // Сразу INSERT
// ❌ persist() для detached сущности с уже установленным ID — EntityExistsException. Если ID генерируется на уровне БД, поведение зависит от стратегии генерации.
User detached = getDetachedUser();
entityManager.persist(detached); // ❌ EntityExistsException
// ✅ merge() для detached
entityManager.merge(detached);
// ❌ Игнорирование return value merge()
entityManager.merge(detached); // результат потерян!
detached.setName("Changed"); // НЕ сохранится!
// ✅ Используйте return value
User managed = entityManager.merge(detached);
managed.setName("Changed"); // сохранится
Generic save метод
public <T extends BaseEntity> T save(T entity) {
if (entity.getId() == null) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
🔴 Senior Level
Внутренняя реализация
persist():
1. check if entity already managed → error if yes
2. check entity state (not detached)
3. schedule INSERT (не выполняет сразу!)
4. add to persistence context
5. entity becomes managed
merge():
1. check if entity with same ID in persistence context
→ yes: use existing managed entity
→ no: SELECT from DB
2. if not in DB: create new managed instance
3. copy state from detached to managed
4. schedule UPDATE if state changed
5. return managed entity
Ключевое отличие:
- persist: original entity becomes managed
- merge: returns NEW managed instance, original stays detached
Производительность
persist():
- 0 SELECT запросов
- 1 INSERT при flush
merge():
- 1 SELECT (проверка существования)
- 1 UPDATE или INSERT при flush
- + копирование состояния
Для новых entities persist() быстрее на 1 SELECT!
Продвинутые сценарии
// Сценарий: обновление с проверкой версии
@Version
private Integer version;
User detached = getDetachedUser();
User managed = entityManager.merge(detached);
// При flush: UPDATE WHERE id = ? AND version = ?
// Если version не совпал → OptimisticLockException
Best Practices
✅ persist() для новых сущностей
✅ merge() для detached сущностей
✅ Проверять ID перед выбором метода
✅ Использовать return value merge()
✅ Понимать что merge возвращает копию
✅ Generic save() с проверкой ID
❌ merge() для новых (лишний SELECT)
❌ persist() для detached (ошибка)
❌ Игнорирование return value merge()
❌ merge() для persistent (лишний SELECT)
🎯 Шпаргалка для интервью
Обязательно знать:
- persist() — для новых сущностей (Transient → Persistent), выполняет INSERT
- merge() — для detached сущностей, возвращает НОВЫЙ managed объект, выполняет UPDATE/INSERT
- persist() быстрее на 1 SELECT (не проверяет существование)
- merge() делает SELECT перед UPDATE/UPDATE — проверяет есть ли entity в БД
- Оригинал после merge() остаётся detached — все операции с returned object
- Generic save(): проверка ID == null → persist, иначе merge
Частые уточняющие вопросы:
- Почему merge() делает SELECT? Чтобы проверить существует ли entity и загрузить текущее состояние для dirty checking
- Что если вызвать persist() для detached? EntityExistsException — Hibernate думает что entity уже managed
- Зачем merge() возвращает новый объект? Оригинальный detached может быть из другой сессии, Hibernate создаёт managed копию в текущей сессии
- Когда merge() делает INSERT? Когда entity с таким ID не существует в БД
Красные флаги (НЕ говорить):
- «merge() для новых сущностей» — лишний SELECT
- «Игнорирую return value merge()» — изменения не сохранятся
- «persist() для detached» — EntityExistsException
- «merge() для persistent» — лишний SELECT, бессмысленно
Связанные темы:
- [[7. Опишите жизненный цикл Entity в Hibernate]]
- [[8. Что такое состояния transient, persistent, detached, removed]]
- [[12. Что такое dirty checking в Hibernate]]
- [[13. Как работает механизм flush в Hibernate]]
- [[15. Что делает метод refresh()]]