Вопрос 14 · Раздел 16

В чём разница между persist() и merge()

persist() и merge() — два ключевых метода EntityManager для управления жизненным циклом сущностей. Понимание разницы между ними критически важно для правильного использования Hi...

Версии по языкам: English Russian Ukrainian

Обзор

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()]]