EntityManager.merge()
может вставлять новые объекты и обновлять существующие.
Почему один хочет использовать persist()
(который может создавать только новые объекты)?
EntityManager.merge()
может вставлять новые объекты и обновлять существующие.
Почему один хочет использовать persist()
(который может создавать только новые объекты)?
Ответы:
В любом случае вы добавите объект в PersistenceContext, разница в том, что вы будете делать с объектом позже.
Persist берет экземпляр объекта, добавляет его в контекст и управляет этим экземпляром (т.е. будущие обновления объекта будут отслеживаться).
Слияние возвращает управляемый экземпляр, в который было объединено состояние. Он возвращает то, что существует в PersistenceContext, или создает новый экземпляр вашей сущности. В любом случае он скопирует состояние из предоставленного объекта и вернет управляемую копию. Экземпляр, который вы передаете, не будет управляемым (любые внесенные вами изменения не будут частью транзакции - если вы не вызовете 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.
merge
, полная копия объекта, прежде чем управлять им, снижает производительность?
@GeneratedId
я могу получить это в сценарии 2?
Persist и merge предназначены для двух разных целей (они вовсе не являются альтернативами).
(отредактировано для расширения информации о различиях)
сохраняются:
слияния:
Persist () эффективность:
Семантика 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 - это программирование очень высокого уровня, и вы не можете предполагать, что это будет иметь место везде.
em.persist(x)
с x = em.merge(x)
?
merge()
может также броситьEntityExistsException
RuntimeException
, но это не упоминается в Javadoc.
Если вы используете назначенный генератор, использование слияния вместо постоянного может привести к созданию избыточного оператора SQL , что повлияет на производительность.
Кроме того, вызов слияния для управляемых объектов также является ошибкой, поскольку Hibernate автоматически управляет управляемыми объектами, и их состояние синхронизируется с записью базы данных с помощью механизма грязной проверки после сброса контекста постоянства .
Чтобы понять, как все это работает, вы должны сначала знать, что Hibernate переключает мышление разработчика с операторов SQL на переходы состояний сущностей .
Когда Hibernate активно управляет сущностью, все изменения будут автоматически распространяться в базу данных.
Hibernate отслеживает подключенные объекты. Но для того, чтобы субъект стал управляемым, он должен находиться в правильном состоянии.
Чтобы лучше понять переходы состояния JPA, вы можете представить следующую диаграмму:
Или, если вы используете специальный API Hibernate:
Как показано на приведенных выше диаграммах, объект может находиться в одном из следующих четырех состояний:
Новый (Переходный)
Вновь созданный объект, который никогда не был связан с Hibernate Session
(aka 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 базы данных будет выполнен во время сброса сеанса.
Я заметил, что когда я использовал em.merge
, я получал SELECT
утверждение для каждого INSERT
, даже когда не было никакого поля, которое JPA генерировал для меня - поле первичного ключа было UUID, который я установил сам. Я переключился на em.persist(myEntityObject)
и получил только INSERT
заявления тогда.
merge()
. У меня была база данных PostgreSQL со сложным представлением : представление агрегированных данных из нескольких таблиц (таблицы имели одинаковую структуру, но разные имена). Так что JPA попытался сделать merge()
, но на самом деле JPA сначала сделал SELECT
(база данных из-за настроек представления может вернуть несколько записей с одним и тем же первичным ключом из разных таблиц!), Затем JPA (Hibernate был реализацией) не удалось: есть несколько записей с одним и тем же ключом ( org.hibernate.HibernateException: More than one row with the given identifier was found
). В моем случае persist()
мне помогли.
В спецификации JPA говорится следующее persist()
.
Если X - это отдельный объект, он
EntityExistsException
может быть сгенерирован, когда вызывается операция persist, или тотEntityExistsException
или иной объект может быть сгенерирован во время сбросаPersistenceException
или фиксации.
Поэтому использование persist()
будет целесообразным, когда объект не должен быть отдельным объектом. Вы можете предпочесть, чтобы код выдавал код, PersistenceException
чтобы он быстро завершался с ошибкой.
Хотя спецификация неясна , persist()
может установить @GeneratedValue
@Id
для объекта. merge()
однако должен иметь объект с @Id
уже сгенерированным.
merge()
однако должен иметь объект с @Id
уже сгенерированным . " Всякий раз, когда EntityManager не находит значение для поля идентификатора объекта, оно сохраняется (вставляется) в БД.
Еще некоторые подробности о слиянии, которые помогут вам использовать слияние по-прежнему:
Возвращение управляемого экземпляра, отличного от исходного объекта, является важной частью процесса слияния. Если экземпляр сущности с тем же идентификатором уже существует в контексте постоянства, провайдер перезапишет свое состояние с состоянием объединяемого объекта, но существующая управляемая версия уже должна быть возвращена клиенту, чтобы она могла быть используемый. Если поставщик не обновил экземпляр Employee в контексте постоянства, любые ссылки на этот экземпляр станут несовместимыми с новым состоянием, в которое выполняется слияние.
Когда merge () вызывается для новой сущности, она ведет себя подобно операции persist (). Он добавляет сущность в контекст постоянства, но вместо добавления исходного экземпляра сущности создает новую копию и управляет этим экземпляром. Копия, созданная операцией merge (), сохраняется, как если бы для нее был вызван метод persist ().
При наличии взаимосвязей операция merge () попытается обновить управляемый объект, чтобы он указывал на управляемые версии объектов, на которые ссылается отдельный объект. Если у сущности есть отношение к объекту, который не имеет постоянной идентичности, результат операции слияния не определен. Некоторые поставщики могут разрешать управляемой копии указывать на непостоянный объект, тогда как другие могут немедленно генерировать исключение. Операция merge () может быть опционально каскадной в этих случаях, чтобы исключить возникновение исключения. Мы расскажем о каскадировании операции merge () позже в этом разделе. Если объединяемая сущность указывает на удаленную сущность, возникает исключение IllegalArgumentException.
Отношения отложенной загрузки являются особым случаем в операции слияния. Если отношение отложенной загрузки не было запущено на объекте до его отсоединения, то это отношение будет игнорироваться при объединении объекта. Если связь была запущена во время управления, а затем была установлена на ноль, пока сущность была отсоединена, управляемая версия сущности также будет очищена во время слияния ».
Вся вышеупомянутая информация была взята из «Pro JPA 2 Mastering Java ™ Persistence API» Майка Кейта и Меррика Шникариола. Глава 6. Раздел отрыв и слияние. Эта книга на самом деле вторая книга, посвященная JPA авторами. Эта новая книга имеет много новой информации, чем прежняя. Я действительно рекомендовал прочитать эту книгу для тех, кто будет серьезно связан с JPA. Прошу прощения за анонимную публикацию моего первого ответа.
Есть еще несколько различий между 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
.
Я получал исключения 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!!
Я нашел это объяснение в документах Hibernate, потому что они содержат пример использования:
Использование и семантика merge (), кажется, вводит в заблуждение новых пользователей. Во-первых, до тех пор, пока вы не пытаетесь использовать состояние объекта, загруженное в одном диспетчере сущностей в другом новом диспетчере сущностей, вам вообще не нужно использовать merge () . Некоторые целые приложения никогда не будут использовать этот метод.
Обычно merge () используется в следующем сценарии:
- Приложение загружает объект в первый менеджер сущностей
- объект передается на уровень представления
- некоторые изменения сделаны для объекта
- объект передается обратно на уровень бизнес-логики
- приложение сохраняет эти изменения, вызывая merge () во втором менеджере сущностей
Вот точная семантика слияния ():
- если есть управляемый экземпляр с тем же идентификатором, который в настоящее время связан с контекстом постоянства, скопируйте состояние данного объекта в управляемый экземпляр
- если в настоящее время нет управляемого экземпляра, связанного с контекстом постоянства, попробуйте загрузить его из базы данных или создать новый управляемый экземпляр
- управляемый экземпляр возвращается
- данный экземпляр не становится связанным с постоянным контекстом, он остается отсоединенным и обычно отбрасывается
От: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Просматривая ответы, не хватает некоторых деталей относительно «Каскад» и генерации идентификатора. См вопрос
Также стоит отметить, что вы можете иметь отдельные Cascade
аннотации для слияния и сохранения: Cascade.MERGE
и Cascade.PERSIST
которые будут обрабатываться в соответствии с используемым методом.
Спекуляция твой друг;)
JPA, бесспорно, большое упрощение в области корпоративных приложений, построенных на платформе Java. Как разработчик, который должен был справиться со сложностями старых компонентов управления данными в J2EE, я считаю включение JPA в спецификации Java EE большим шагом вперед. Однако, углубляясь в детали JPA, я нахожу вещи, которые не так просты. В этой статье я имею дело со сравнением методов слияния и сохранения EntityManager, чье перекрывающееся поведение может вызвать путаницу не только у новичка. Кроме того, я предлагаю обобщение, которое рассматривает оба метода как частные случаи объединения более общего метода.
Постоянные сущности
В отличие от метода слияния, метод persist довольно прост и интуитивен. Наиболее распространенный сценарий использования метода persist можно описать следующим образом:
«Вновь созданный экземпляр класса сущностей передается методу persist. После возврата из этого метода сущность управляется и планируется для вставки в базу данных. Это может произойти во время или до совершения транзакции, или когда вызывается метод flush. Если объект ссылается на другой объект посредством отношения, помеченного каскадной стратегией PERSIST, эта процедура также применяется к нему ".
Спецификация более детальна, однако, помнить их не важно, поскольку эти детали охватывают более или менее экзотические ситуации.
Слияние сущностей
По сравнению с сохранением, описание поведения слияния не так просто. Здесь нет основного сценария, как в случае с постоянным, и программист должен помнить все сценарии, чтобы написать правильный код. Мне кажется, что разработчики JPA хотели иметь какой-то метод, основной задачей которого была бы обработка отсоединенных сущностей (в отличие от метода persist, который в первую очередь имеет дело с вновь создаваемыми сущностями). Основная задача метода слияния состоит в передаче состояния из неуправляемый объект (переданный в качестве аргумента) своему управляемому аналогу в контексте постоянства. Эта задача, однако, делится далее на несколько сценариев, которые ухудшают разборчивость поведения метода в целом.
Вместо того, чтобы повторять абзацы из спецификации JPA, я подготовил блок-схему, которая схематически изображает поведение метода слияния:
Итак, когда я должен использовать постоянный и когда объединить?
упорствовать
слияние
Сценарий 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()
не будет жаловаться, что у него есть идентификатор, он только будет жаловаться, когда что-то с таким же идентификатором уже есть в базе данных.
Возможно, вы пришли сюда за советом о том, когда использовать persist и когда использовать merge . Я думаю, что это зависит от ситуации: насколько вероятно, что вам нужно создать новую запись, и насколько сложно получить постоянные данные.
Предположим, вы можете использовать натуральный ключ / идентификатор.
Данные должны быть сохранены, но время от времени существует запись и требуется обновление. В этом случае вы можете попробовать персистирование, и если оно выдает исключение EntityExistsException, вы ищите его и объединяете данные:
try {entityManager.persist (entity)}
catch (исключение EntityExistsException) {/ * получить и объединить * /}
Сохраняемые данные необходимо обновить, но время от времени для этих данных еще нет записей. В этом случае вы ищете его и сохраните, если объект отсутствует:
entity = entityManager.find (ключ);
if (entity == null) {entityManager.persist (entity); }
еще {/ * объединить * /}
Если у вас нет естественного ключа / идентификатора, вам будет сложнее выяснить, существует сущность или нет, или как ее найти.
Слияния можно обрабатывать двумя способами:
persist (entity) должен использоваться с абсолютно новыми сущностями, чтобы добавить их в БД (если сущность уже существует в БД, будет выброшено исключение EntityExistsException).
Слияние (сущность) должно использоваться, чтобы вернуть сущность обратно в контекст постоянства, если сущность была отсоединена и была изменена.
Вероятно, persist генерирует оператор INSERT SQL и объединяет оператор UPDATE SQL (но я не уверен).