JPA EntityManager ( Менеджер объектов JPA):Зачем использовать persist() вместо merge()?

StackOverflow https://stackoverflow.com/questions/1069992

Вопрос

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

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

Это было полезно?

Решение

В любом случае вы добавите объект в 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.

Другие советы

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

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

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

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

слияние:

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

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

  • Это могло бы быть более эффективным для вставки нового регистра в базу данных, чем 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 прикрепленный объект для любого регистра в entity manager.

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 с опцией ОБНОВЛЕНИЯ ДУБЛИРОВАННОГО КЛЮЧА, JPA - это программирование очень высокого уровня, и вы не можете предположить, что это будет иметь место везде.

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

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

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

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

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

Во-первых, мы должны определить все состояния объекта:

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

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

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

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

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

  • Отстраненный

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

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

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

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

    • Слияние

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

  • Удалено

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

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

enter image description here

Или если вы используете специфичный для гибернации API:

enter image description here

Я заметил это, когда использовал 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Сохраняющиеся объекты

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

"Вновь созданный экземпляр класса entity передается методу persist.После возврата этого метода объект управляется и планируется для вставки в базу данных.Это может произойти во время или до фиксации транзакции, или при вызове метода flush.Если объект ссылается на другой объект через связь, помеченную каскадной стратегией СОХРАНЕНИЯ, эта процедура применяется и к нему ".

enter image description here

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

Объединяющиеся объекты

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

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

enter image description here

Итак, когда я должен использовать persist и когда merge?

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

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

слияние

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

Сценарий X:

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

Этот сценарий приводит к экономии :Плевательница и обе Плевательницы как будто принадлежат одному и тому же Плевателю.

        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:

Это сохранит Плевок, сохранит 2 плевка, Но они не будут ссылаться на один и тот же Плевок!

        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.спящий режим.PersistentObjectException:отделенный объект передан для сохранения

hibernate-в jpa javadoc есть подсказка:

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

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

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

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

    попробуйте { EntityManager.persist(сущность) }

    catch(исключение EntityExistsException) { /* извлекать и объединять */ }

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

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

    if (entity == null) { EntityManager.persist(сущность);}

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

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

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

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

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

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

Вероятно, persist генерирует инструкцию INSERT sql и инструкцию merge UPDATE sql (но я не уверен).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top