JPA EntityManager: зачем использовать persist() вместо merge()?

EntityManager.merge() может вставлять новые объекты и обновлять существующие.

Почему один хочет использовать persist() (который может создавать только новые объекты)?

16 ответов

Решение

В любом случае вы добавите объект в PersistenceContext, разница в том, что вы будете делать с объектом позже.

Persist берет экземпляр объекта, добавляет его в контекст и управляет этим экземпляром (т.е. будущие обновления объекта будут отслеживаться).

Merge создает новый экземпляр вашей сущности, копирует состояние из предоставленной сущности и управляет новой копией. Экземпляр, который вы передаете, не будет управляемым (любые внесенные вами изменения не будут частью транзакции - если вы не вызовете merge снова).

Может быть, пример кода поможет.

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)

// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

Сценарии 1 и 3 примерно эквивалентны, но в некоторых ситуациях вы хотите использовать сценарий 2.

Persist и merge предназначены для двух разных целей (они вовсе не являются альтернативами).

(отредактировано для расширения информации о различиях)

сохраняются:

  • Вставить новый регистр в базу данных
  • Присоедините объект к менеджеру сущностей.

слияния:

  • Найдите прикрепленный объект с тем же идентификатором и обновите его.
  • Если существует, обновите и верните уже прикрепленный объект.
  • Если не существует, вставьте новый регистр в базу данных.

постоянная () эффективность:

  • Это может быть более эффективным для вставки нового регистра в базу данных, чем merge().
  • Это не дублирует оригинальный объект.

Семантика persist():

  • Это гарантирует, что вы вставляете, а не обновляете по ошибке.

Пример:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

Таким образом, существует только 1 прикрепленный объект для любого регистра в менеджере сущностей.

merge () для сущности с идентификатором выглядит примерно так:

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

Хотя при подключении к MySQL функция merge () может быть столь же эффективной, как и persist(), использующей вызов INSERT с опцией ON DUPLICATE KEY UPDATE, JPA является программированием очень высокого уровня, и вы не можете предполагать, что это будет иметь место везде.

Если вы используете назначенный генератор, использование слияния вместо постоянного может привести к созданию избыточного оператора SQL, что повлияет на производительность.

Кроме того, вызов слияния для управляемых объектов также является ошибкой, поскольку Hibernate автоматически управляет управляемыми объектами, а их состояние синхронизируется с записью базы данных с помощью механизма грязной проверки после сброса контекста персистентности.

Чтобы понять, как все это работает, вы должны сначала знать, что Hibernate переключает мышление разработчика с операторов SQL на переходы состояний сущностей.

Когда Hibernate активно управляет сущностью, все изменения будут автоматически распространяться в базу данных.

Hibernate отслеживает подключенные объекты. Но для того, чтобы субъект стал управляемым, он должен быть в правильном состоянии.

Во-первых, мы должны определить все состояния сущности:

  • Новый (Переходный)

    Недавно созданный объект, который никогда не был связан с Hibernate Session (ака Persistence Context) и не сопоставляется ни с одной строкой таблицы базы данных, считается, что она находится в состоянии New (Transient).

    Чтобы стать настойчивым, нам нужно либо явно вызвать EntityManager#persist метод или использовать механизм транзитивной персистентности.

  • Постоянный (управляемый)

    Постоянный объект был связан со строкой таблицы базы данных, и он управляется текущим запущенным контекстом постоянства. Любые изменения, внесенные в такой объект, будут обнаружены и распространены в базе данных (во время сброса сеанса). С Hibernate нам больше не нужно выполнять операторы INSERT/UPDATE/DELETE. В Hibernate используется транзакционный стиль записи с обратной записью, а изменения синхронизируются в самый последний ответственный момент, в течение текущего Session вровень время.

  • отдельный

    Как только текущий запущенный контекст постоянства закрыт, все ранее управляемые объекты становятся отсоединенными. Последовательные изменения больше не будут отслеживаться, и автоматическая синхронизация базы данных не произойдет.

    Чтобы связать отдельную сущность с активным сеансом Hibernate, вы можете выбрать один из следующих вариантов:

    • Повторное прикрепление

      Hibernate (но не JPA 2.1) поддерживает подключение через метод Session#update. Сеанс Hibernate может связать только один объект Entity для данной строки базы данных. Это связано с тем, что постоянный контекст действует как кэш в памяти (кэш первого уровня) и только одно значение (сущность) связано с данным ключом (тип сущности и идентификатор базы данных). Объект может быть присоединен повторно, только если нет другого объекта JVM (соответствующего той же строке базы данных), уже связанного с текущим сеансом Hibernate.

    • сращивание

    Слияние собирается скопировать состояние отдельного объекта (источник) в экземпляр управляемого объекта (место назначения). Если объединяемый объект не имеет эквивалента в текущем сеансе, он будет выбран из базы данных. Экземпляр отсоединенного объекта будет оставаться отсоединенным даже после операции объединения.

  • Удалены

    Хотя JPA требует, чтобы разрешалось удалять только управляемые объекты, Hibernate также может удалять отдельные объекты (но только с помощью вызова метода Session#delete). Удаленный объект запланирован только для удаления, и фактический оператор DELETE базы данных будет выполнен во время сброса сеанса.

Чтобы лучше понять переходы состояния JPA, вы можете представить следующую диаграмму:

Или, если вы используете специальный API Hibernate:

Я заметил, что когда я использовал em.merge, Я получил SELECT заявление для каждого INSERT, даже когда не было поля, которое JPA генерировал для меня - поле первичного ключа было UUID, который я установил сам. Я перешел на em.persist(myEntityObject) и получил только INSERT заявления тогда.

В спецификации JPA говорится следующее persist(),

Если X является отдельным объектом, EntityExistsException может быть сгенерировано, когда вызывается постоянная операция, или EntityExistsException или другой PersistenceException может быть брошено на флеш или совершить время.

Итак, используя persist() было бы целесообразно, когда объект не должен быть отдельным объектом. Вы могли бы предпочесть, чтобы код бросил PersistenceException так что быстро терпит неудачу.

Хотя спецификация неясна, persist() может установить @GeneratedValue@Id для объекта. merge() Однако должен иметь объект с @Id уже сгенерировано.

Есть еще несколько различий между merge а также persist (Я перечислю еще раз те, которые уже размещены здесь):

D1. merge не делает переданный объект управляемым, а возвращает другой управляемый экземпляр. persist с другой стороны сделаем переданный объект управляемым:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2. Если вы удалите сущность, а затем решите сохранить ее обратно, вы можете сделать это только с помощью persist(), потому что merge бросит IllegalArgumentException,

D3. Если вы решили позаботиться о своих идентификаторах вручную (например, используя UUID), тогда merge операция вызовет последующую SELECT запросы для поиска существующих объектов с этим идентификатором, в то время как persist возможно, не нужны эти запросы.

D4. Есть случаи, когда вы просто не доверяете коду, который вызывает ваш код, и чтобы убедиться, что данные не обновляются, а вставляются, вы должны использовать persist,

Еще несколько подробностей о слиянии, которые помогут вам использовать слияние по-прежнему:

Возвращение управляемого экземпляра, отличного от исходного объекта, является важной частью процесса слияния. Если экземпляр сущности с таким же идентификатором уже существует в контексте постоянства, провайдер перезапишет свое состояние состоянием объединяемого объекта, но существующая управляемая версия уже должна быть возвращена клиенту, чтобы ее можно было перезаписать. используемый. Если поставщик не обновил экземпляр Employee в контексте постоянства, любые ссылки на этот экземпляр станут несовместимыми с новым состоянием, в которое выполняется слияние.

Когда merge() вызывается для новой сущности, она ведет себя аналогично операции persist(). Он добавляет сущность в контекст постоянства, но вместо добавления исходного экземпляра сущности создает новую копию и управляет этим экземпляром. Копия, созданная операцией merge(), сохраняется, как если бы для нее был вызван метод persist().

При наличии взаимосвязей операция merge() попытается обновить управляемый объект, чтобы он указывал на управляемые версии объектов, на которые ссылается отдельный объект. Если сущность имеет отношение к объекту, который не имеет постоянной идентичности, результат операции слияния не определен. Некоторые поставщики могут разрешать управляемой копии указывать на непостоянный объект, тогда как другие могут немедленно генерировать исключение. Операция merge() может быть опционально каскадной в этих случаях, чтобы исключить возникновение исключения. Мы расскажем о каскадировании операции merge() позже в этом разделе. Если объединяемая сущность указывает на удаленную сущность, генерируется исключение IllegalArgumentException.

Отношения отложенной загрузки являются частным случаем в операции слияния. Если отношение отложенной загрузки не было запущено для объекта до его отсоединения, то это отношение будет игнорироваться при объединении объекта. Если связь была запущена во время управления, а затем была установлена ​​на ноль, пока сущность была отсоединена, управляемая версия сущности также будет очищена во время слияния ".

Вся вышеупомянутая информация была взята из "Pro JPA 2 Mastering Java™ Persistence API" Майка Кейта и Меррика Шникариола. Глава 6. Раздел отрыв и слияние. Эта книга на самом деле вторая книга, посвященная JPA авторами. Эта новая книга имеет много новой информации, чем прежняя. Я действительно рекомендовал прочитать эту книгу для тех, кто будет серьезно связан с JPA. Прошу прощения за анонимную публикацию моего первого ответа.

JPA, бесспорно, большое упрощение в области корпоративных приложений, построенных на платформе Java. Как разработчик, который должен был справиться со сложностями старых компонентов управления данными в J2EE, я вижу включение JPA в спецификации Java EE как большой шаг вперед. Однако, углубляясь в детали JPA, я нахожу вещи, которые не так просты. В этой статье я имею в виду сравнение методов слияния и сохранения EntityManager, чье перекрывающееся поведение может вызвать путаницу не только у новичка. Кроме того, я предлагаю обобщение, которое рассматривает оба метода как частные случаи объединения более общего метода.

Постоянные сущности

В отличие от метода слияния, метод persist довольно прост и интуитивен. Наиболее распространенный сценарий использования постоянного метода можно суммировать следующим образом:

"Вновь созданный экземпляр класса сущности передается методу persist. После возврата из этого метода сущность управляется и планируется для вставки в базу данных. Это может произойти во время или до совершения транзакции, или когда вызывается метод flush. Если объект ссылается на другой объект через отношение, помеченное каскадной стратегией PERSIST, эта процедура также применяется к нему ".

Спецификация углубляется в детали, однако их запоминание не является критически важным, поскольку эти детали охватывают более или менее экзотические ситуации.

Слияние сущностей

По сравнению с сохранением, описание поведения слияния не так просто. Здесь нет основного сценария, как в случае с постоянным, и программист должен помнить все сценарии, чтобы написать правильный код. Мне кажется, что разработчики JPA хотели иметь какой-то метод, основной задачей которого была бы обработка отсоединенных сущностей (в отличие от метода persist, который в первую очередь имеет дело с вновь создаваемыми сущностями). Основная задача метода слияния - передать состояние из неуправляемый объект (переданный в качестве аргумента) своему управляемому аналогу в контексте постоянства. Эта задача, однако, делится далее на несколько сценариев, которые ухудшают разборчивость поведения метода в целом.

Вместо того, чтобы повторять абзацы из спецификации JPA, я подготовил блок-схему, которая схематически изображает поведение метода слияния:

Итак, когда я должен использовать постоянный и когда объединить?

упорствовать

  • Вы хотите, чтобы метод всегда создавал новую сущность и никогда не обновлял сущность. В противном случае метод генерирует исключение в результате нарушения уникальности первичного ключа.
  • Пакетные процессы, обрабатывающие объекты в состоянии (см. Шаблон шлюза).
  • Оптимизация производительности

сливаться

  • Вы хотите, чтобы метод либо вставлял, либо обновлял сущность в базе данных.
  • Вы хотите обрабатывать объекты без сохранения состояния (объекты передачи данных в сервисах)
  • Вы хотите вставить новую сущность, которая может иметь ссылку на другую сущность, которая может быть создана, но еще не может быть создана (отношение должно быть помечено как MERGE). Например, вставка новой фотографии со ссылкой на новый или уже существующий альбом.

Я нашел это объяснение в документах Hibernate, потому что они содержат пример использования:

Использование и семантика merge(), кажется, сбивает с толку новых пользователей. Во-первых, до тех пор, пока вы не пытаетесь использовать состояние объекта, загруженное в одном диспетчере сущностей в другом новом диспетчере сущностей, вам вообще не нужно использовать merge(). Некоторые целые приложения никогда не будут использовать этот метод.

Обычно merge() используется в следующем сценарии:

  • Приложение загружает объект в первый менеджер сущностей
  • объект передается на уровень представления
  • некоторые изменения сделаны для объекта
  • объект передается обратно на уровень бизнес-логики
  • приложение сохраняет эти изменения, вызывая merge() во втором менеджере сущностей

Вот точная семантика слияния ():

  • если существует управляемый экземпляр с тем же идентификатором, который в настоящее время связан с контекстом постоянства, скопируйте состояние данного объекта в управляемый экземпляр
  • если в данный момент нет управляемого экземпляра, связанного с контекстом постоянства, попробуйте загрузить его из базы данных или создать новый управляемый экземпляр
  • управляемый экземпляр возвращается
  • данный экземпляр не становится связанным с постоянным контекстом, он остается отсоединенным и обычно отбрасывается

От: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html

Я получал исключения lazyLoading для моей сущности, потому что я пытался получить доступ к загруженной ленивой коллекции, которая находилась в сеансе.

То, что я сделал бы, было в отдельном запросе, получить сущность из сеанса и затем попытаться получить доступ к коллекции на моей странице JSP, которая была проблематичной.

Чтобы облегчить это, я обновил ту же сущность в моем контроллере и передал ее в мой jsp, хотя при повторном сохранении в сеансе я представляю, что она также будет доступна, хотя SessionScope и не бросай LazyLoadingExceptionмодификация примера 2:

Следующее сработало для меня:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!

Просматривая ответы, не хватает некоторых деталей, касающихся "Каскад" и генерации идентификатора. См вопрос

Также стоит упомянуть, что вы можете иметь отдельные Cascade аннотации для слияния и сохранения: Cascade.MERGE а также Cascade.PERSIST который будет обрабатываться в соответствии с использованным методом.

Спекуляция твой друг;)

Сценарий X:

Таблица:Spitter (Один), Таблица: Spittles (Многие) (Spittles является владельцем отношений с FK:spitter_id)

Этот сценарий приводит к сохранению: Spitter и обоих Spittles, как если бы они принадлежали Same Spitter.

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

Сценарий Y:

Это сохранит Spitter, сохранит 2 Spittles, но они не будут ссылаться на тот же Spitter!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

Еще одно наблюдение:

merge() будет заботиться только об автоматически сгенерированном идентификаторе (проверено на IDENTITY а также SEQUENCE) когда запись с таким идентификатором уже существует в вашей таблице. В таком случае merge() постараюсь обновить запись. Однако, если идентификатор отсутствует или не соответствует ни одной из существующих записей, merge() полностью проигнорирует это и попросит БД выделить новый. Иногда это является источником множества ошибок. Не использовать merge() заставить идентификатор для новой записи.

persist() с другой стороны, вы никогда не позволите вам даже передать ему идентификатор. Это немедленно провалится. В моем случае это:

Вызывается: org.hibernate.PersistentObjectException: отдельная сущность передана для сохранения

hibernate-jpa javadoc имеет подсказку:

Выдает: javax.persistence.EntityExistsException - если объект уже существует. (Если сущность уже существует, исключение EntityExistsException может генерироваться при вызове операции persist, либо может создаваться исключение EntityExistsException или другое PersistenceException во время сброса или фиксации.)

Возможно, вы пришли сюда за советом о том, когда использовать persist и когда использовать merge. Я думаю, что это зависит от ситуации: насколько вероятно, что вам нужно создать новую запись, и насколько сложно получить постоянные данные.

Предположим, вы можете использовать естественный ключ / идентификатор.

  • Данные должны быть сохранены, но время от времени существует запись и требуется обновление. В этом случае вы можете попробовать персистирование, и если оно выдает исключение EntityExistsException, вы ищите его и объединяете данные:

    try {entityManager.persist (entity)}

    catch (исключение EntityExistsException) { /* получить и объединить */ }

  • Сохраняемые данные необходимо обновить, но время от времени для этих данных еще нет записей. В этом случае вы ищете его и сохраните, если объект отсутствует:

    entity = entityManager.find (ключ);

    if (entity == null) {entityManager.persist (entity); }

    еще {/ * объединить */ }

Если у вас нет естественного ключа / идентификатора, вам будет сложнее выяснить, существует ли сущность или нет, или как ее найти.

Слияния можно обрабатывать двумя способами:

  1. Если изменения обычно невелики, примените их к управляемому объекту.
  2. Если изменения являются общими, скопируйте идентификатор из сохраняемой сущности, а также неизмененные данные. Затем вызовите EntityManager::merge() для замены старого содержимого.

Слияние не будет обновлять переданный объект, если этот объект не является управляемым. Даже если для идентификатора объекта задана существующая запись БД, в базе данных будет создана новая запись.

persist(entity) должен использоваться с абсолютно новыми сущностями, чтобы добавить их в БД (если сущность уже существует в БД, будет выброшено исключение EntityExistsException).

Слияние (сущность) должно использоваться, чтобы вернуть сущность обратно в контекст постоянства, если сущность была отсоединена и была изменена.

Вероятно, persist генерирует оператор INSERT SQL и объединяет оператор UPDATE SQL (но я не уверен).

Другие вопросы по тегам