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

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

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

Ответ 1

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

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

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.

Ответ 2

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

(отредактировано, чтобы развернуть информацию о различиях)

persist:

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

merge:

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

persist():

  • Это может быть более эффективным для вставки нового регистра в базу данных, чем 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);
}

В этом случае существует только один прикрепленный объект для любого регистра в менеджере сущностей.

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 - очень высокоуровневое программирование, и вы не можете предположить, что это будет случай везде.

Ответ 3

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

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

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

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

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

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

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

    Вновь созданный объект, который hasnt когда - либо был связан с Hibernate Session (он же Persistence Context) и не отображается в любой таблице базы данных строки считается в (транзиторной) состояние нового.

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

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

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

  • отдельный

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

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

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

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

    • сращивание

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

  • Удалены

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

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

enter image description here

Или если вы используете API-интерфейс Hibernate:

enter image description here

Ответ 4

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

Ответ 5

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

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

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

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

Ответ 6

Некоторые подробности о слиянии, которые помогут вам использовать слияние, сохраняются:

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

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

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

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

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

Ответ 7

Есть еще несколько различий между 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 могут не нуждаться в этих запросах.

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

Ответ 8

Я получал 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!!

Ответ 9

Проходя ответы, есть некоторые недостатки в отношении `Cascade 'и генерации id. См. вопрос

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

Спецификация - ваш друг;)

Ответ 10

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

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

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

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

enter image description here

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

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

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

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

enter image description here

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

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

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

сливаться

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

Ответ 11

Сценарий X:

Таблица: Spitter (One), Table: Spittles (Many) (Spittles является владельцем отношения с FK: spitter_id)

Этот сценарий приводит к сохранению: The 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!!

Ответ 12

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

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

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

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

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

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

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

Ответ 13

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

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

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

    try {entityManager.persist(entity)}

    catch (исключение исключения EntityExistsException) {/* retrieve and merge */}

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

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

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

    else {/* merge */}

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

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

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

Ответ 14

Другое наблюдение:

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

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

Вызвано: org.hibernate.PersistentObjectException: удаленный объект передан для сохранения

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

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

Ответ 15

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

merge (entity) следует использовать, чтобы вернуть объект обратно в контекст персистентности, если объект был отсоединен и был изменен.

Вероятно, persist генерирует инструкцию SQLSERT и объединяет оператор UPDATE sql (но я не уверен).