неділя, 1 квітня 2012 р.

JPA 2.0: Persist vs Merge

Интересная вещь мне открылась, когда я решал проблему, традиционную для JPA, есть классическая ORM модель, в которой необходимо создать новый объект, но связать его с уже существующими данными. Довольно часто здесь возникает javax.persistence.PersistenceException, связанный с тем, что мы пытаемся вставить в базу данных объект, находящийся в состоянии DETACHED. Мой метод saveOrUpdate выглядел традицоинным образом
public T saveOrUpdate(final T entity) { 
    if (entity.getId() == null) { 
        entityManager.persist(entity); 
        entityManger.refresh(entity); 
    } else { 
        entityManger.merge(entity); 
    } 
    return entity; 
}
Так я использовал его множество раз и множество подобных примеров встречал в интернете. Моя наибольшая проблема заключалась в том, что модель приложения весьма обширна и вручную обрабатывать все связные классы на предмет их наличия в базе данных заняло бы очень много времени. Поэтому я стал искать более элегатное решение. И нашел его, но не совсем там, где расчитывал. А именно в документации по JPA. Подробное описание различия между методами persist и merge, я нашел в статье Zbyněk Šlajchrt. А я хотел бы поделится выводы, которые из этой статьи сделал:

Как это работает?

persist

Всегда генерирует INSERT statement. Автоматически анализирует, является ли объект новой записью для базы данных, и если нет - генерирует javax.persistence.PersistenceException

merge

Геренрирует INSERT или UPDATE statement, в зависимости от состояния объекта. Иными словами, реализация метода сама способна определить, какие entity, в переданном дереве объектов, необходимо вставить, а какие - проапдейтить.

Когда что использовать?

persist

В случае, когда persistence layer приложения выполнен в соответствии со STATEFULL стратегией, а следовательно в число обязанностей логики работы с persistence входит: 1. Полный контроль состояния объектов 2. Определение является ли входящий entity новым или уже существующем 3. Автоматическое обновление тех узлов входящего дерева объектов, которые являются существующими 4. Разрешение конфликтных ситуаций, когда некоторые из узлов входящего дерева объектов являются новыми, а иные - уже существующими.

merge

В случае, когда persistence layer приложения выполнен в соотвествии со STATELESS стратегией, а следовательно все обязанности по вставке новых entity, обновлению существующих, разрешение конфликтов на уровне дерева объектов мы бы хотели делегировать JPA

Summary

Если в некоем приложении предполагается развернутая логика DAO и сервисов уровня persistence; если изначально запланировал тотальный контроль над состоянием модели и процессом ее изменения; если предполагается “вручную” контролировать сотояния объектов и “вручную” же решать что необходимо вставить, что - обновить, а что не обновлять; если логика persistence уровня приложения должна опять таки “вручную” анализировать все связи всех объектов и обновлять внешние ключи новых записей соотвестующими значениями уже существующих - только в этом случае есть необходимость использовать persist. Во всех остальных случаях функционал merge подходит более чем полность.
Как читатель наверняка догадался, моя проблема решилась заменой такого кода
public T saveOrUpdate(final T entity) { 
    if (entity.getId() == null) { 
        entityManager.persist(entity); 
        entityManger.refresh(entity); 
    } else { 
        entityManger.merge(entity); 
    } 
    return entity; 
}
на вот такой
public T saveOrUpdate(final T entity) { return entityManger.merge(entity); }
P.S. Лично мне это напоминает историю с BMP и CMP Entity Beans из специфкации EJB 2.0/2.1. Там, разумеется, масштабы и задачи были другие, но все-равно Bean Managed Persistence использовался либо на уж сверхкритичных по производительности участках, либо от не знания и не понимания разницы между Bean и Conainer Managed Persistence. Для подавляющего большинства задач тогда было достаточно CMP Entity Beans, равно как для подавляющего большинства задач сейчас достаточно прото вызвать метод merge и возможно (только возможно!), в особых случаях, когда реализация JPA бажит, реализовывать ручную обработку.