Статьи

Шаблоны реализации JPA: сохранение (отсоединение) объектов

Мы начали поиск шаблонов реализации JPA с шаблоном Data Access Object и продолжили обсуждение того, как управлять двунаправленными ассоциациями . На этой неделе мы затрагиваем тему, которая на первый взгляд может показаться тривиальной: как спасти сущность.

Сохранить сущность в JPA просто, верно? Мы просто передаем объект, который хотим сохранить, в EntityManager.persist . Кажется, что все работает довольно хорошо, пока мы не натолкнемся на страшное сообщение «отделенная сущность передана для сохранения» . Или аналогичное сообщение, когда мы используем поставщика JPA, отличного от Hibernate EntityManager.


A: ссылка {цвет: синий;} A: посещение {цвет: # A000A0;} html body p {line-height: 120%;
} ul li, ul.menu li, .item-list ul li, li.leaf {list-style-type: disc; list-style-position: outside;} ol li, ol.menu li, .item-list ol li, li.leaf {list-style-type: decimal; список-стиль-позиция: снаружи;}

Так что же это за отдельная сущность, о которой говорится в сообщении? Отделенный объект (он же отделенный объект) — это объект, который имеет тот же идентификатор, что и объект в постоянном хранилище, но больше не является частью постоянного контекста (области действия сеанса EntityManager). Две наиболее распространенные причины этого:

  • EntityManager, из которого был получен объект, был закрыт .
  • Объект был получен извне нашего приложения, например, как часть отправки формы, протокола удаленного взаимодействия, такого как Hessian , или по каналу BlazeDS AMF от клиента Flex.

Контракт для persist (см. Раздел 3.2.1 спецификации JPA 1.0 ) явно устанавливает, что EntityExistsException вызывается методом persist, когда переданный объект является отсоединенной сущностью. Или любое другое PersistenceException, когда контекст постоянства сбрасывается или транзакция фиксируется . Обратите внимание, что это непроблема сохранения одного и того же объекта дважды в одной транзакции. Второй вызов будет просто проигнорирован, хотя операция persist может каскадно связываться с любыми ассоциациями сущности, которые были добавлены с момента первого вызова. Помимо этого последнего соображения нет необходимости вызывать EntityManager.persist для уже сохраненного объекта, потому что любые изменения будут автоматически сохраняться во время сброса или фиксации.

saveOrUpdate против слияния

Те из вас, кто работал с обычным Hibernate, вероятно, уже привыкли использовать метод Session.saveOrUpdate для сохранения сущностей. Метод saveOrUpdate выясняет, является ли объект новым или уже был сохранен ранее. В первом случае сущность сохраняется , во втором — обновляется .

При переходе с Hibernate на JPA многие люди встревожены тем, что этот метод отсутствует . Ближайшая альтернатива — это метод EntityManager.merge , но есть большая разница, которая имеет важные последствия. Метод Session.saveOrUpdate и его двоюродный брат Session.update присоединяют переданный объект к контексту постоянства, а метод EntityManager.merge копирует состояние переданного объекта в постоянный объект с тем же идентификатором, а затем возвращает ссылку на этот постоянный объект. , Переданный объект не привязан к контексту постоянства.

Это означает, что после вызова EntityManager.merge мы должны использовать ссылку на сущность, возвращенную из этого метода, вместо исходного объекта, переданного внутрь. Это отличается от способа, которым можно просто вызывать EntityManager.persist для объекта (даже несколько раз как упомянуто выше!), чтобы сохранить это и продолжить использовать оригинальный объект. Session.saveOrUpdate библиотеки Hibernate делает поведение акций , что хороший с EntityManager.persist (или , вернее , Session.save) , даже при обновлении, но у него есть один большой недостаток; если объект с тем же идентификатором, что и тот, который мы пытаемся обновить, т. е. присоединить, уже является частью контекста постоянства, NonUniqueObjectExceptionброшен И выяснить, какой фрагмент кода сохранился (или объединить или извлечь), что другой объект сложнее, чем выяснить, почему мы получаем сообщение «отделенный объект передан для сохранения».

Собираем все вместе

Итак, давайте рассмотрим три возможных случая и то, что делают разные методы:

сценарий EntityManager.persist EntityManager.merge SessionManager.saveOrUpdate
Переданный объект никогда не был сохранен 1. Объект добавлен в контекст постоянства как новый объект
2. Новый объект вставлен в базу данных при сбросе / фиксации
1. Государство скопировано в новое юридическое лицо.
2. Новая сущность добавлена ​​в контекст постоянства
3. Новая сущность вставлена ​​в базу данных при сбросе / фиксации
4. Новая сущность возвращена
1. Объект добавлен в контекст постоянства как новый объект
2. Новый объект вставлен в базу данных при сбросе / фиксации
Объект был ранее сохранен, но не загружен в этом контексте постоянства 1. Исключение EntityExistsException (или исключение PersistenceException при сбросе / фиксации) 2. Существующий объект загружен.
2. Состояние, скопированное из объекта в загруженный объект.
3. Загруженный объект обновляется в базе данных при очистке / фиксации.
4. Возвращенный загруженный объект.
1. Объект добавлен в контекст постоянства.
2. Загруженный объект обновляется в базе данных при сбросе / фиксации.
Объект был ранее сохранен и уже загружен в этом контексте постоянства 1. Бросок EntityExistsException (или PersistenceException во время сброса или фиксации) 1. Состояние из объекта, скопированного в загруженный объект
2. Загруженный объект обновляется в базе данных при сбросе / фиксации
3. Возвращенный загруженный объект
1. Неуникальное исключение объекта

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

Проблема с слиянием

Прежде чем продолжить, нам нужно обсудить один недостаток способа работы EntityManager.merge; это может легко сломать двунаправленные ассоциации. Рассмотрим пример с классами Order и OrderLine из предыдущего блога этой серии . Если обновленный объект OrderLine получен из веб-интерфейса (или от гессенского клиента, или из приложения Flex, и т. Д.), Поле порядка может быть установлено равным нулю. Если этот объект затем объединяется с уже загруженной сущностью, поле заказа этой сущности устанавливается равным нулю. Но он не будет удален из набора orderLines Order, на который он ссылался, тем самым нарушая инвариант, в котором каждый элемент в наборе orderLines Order имеет свое поле порядка, указывающее на этот Order.

В этом случае или в других случаях, когда упрощенный способ, которым EntityManager.merge копирует состояние объекта в загруженную сущность, вызывает проблемы, мы можем вернуться к шаблону слияния DIY . Вместо вызова EntityManager.merge мы вызываем EntityManager.find, чтобы найти существующую сущность и самостоятельно скопировать состояние. Если EntityManager.find возвращает null, мы можем решить, сохранить ли полученный объект или сгенерировать исключение. Применительно к классу Order этот шаблон может быть реализован так:

Order existingOrder = dao.findById(receivedOrder.getId());if(existingOrder == null) {dao.persist(receivedOrder);} else {existingOrder.setCustomerName(receivedOrder.getCustomerName());existingOrder.setDate(receivedOrder.getDate());}

Шаблон

Так, где все это оставляет нас? Эмпирическое правило, которого я придерживаюсь, таково:

  • Когда и только когда (и предпочтительно где) мы создаем новую сущность, вызываем EntityManager.persist, чтобы сохранить ее. Это имеет смысл, когда мы рассматриваем наши объекты доступа к домену как коллекции . Я называю это паттерном упорства на новом .
  • При обновлении существующей сущности мы не вызываем никакой метод EntityManager; JPA-провайдер автоматически обновит базу данных во время сброса или фиксации.
  • Когда мы получаем обновленную версию существующей простой сущности (сущности без ссылок на другие сущности) извне нашего приложения и хотим сохранить новое состояние, мы вызываем EntityManager.merge, чтобы скопировать это состояние в контекст постоянства. Из-за способа слияния мы можем сделать это, если не уверены, что объект уже сохранен.
  • Когда нам нужно больше контроля над процессом слияния, мы используем шаблон слияния DIY .

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

С http://blog.xebia.com