Статьи

Шаблоны реализации JPA: удаление сущностей

Как и при извлечении объекта, удаление объекта довольно просто. Фактически это все, что вам нужно сделать, это передать сущность методу EntityManager.remove, чтобы удалить сущность из базы данных, когда транзакция зафиксирована (конечно, вы фактически вызываете метод удаления в DAO, который, в свою очередь, вызывает EntityManager. удаленный). Это все, что нужно сделать. Обычно. Потому что, когда вы используете ассоциации ( двунаправленные или нет), все становится интереснее.

Удаление сущностей, которые являются частью ассоциации

Рассмотрим пример с классами Order и OrderLine, которые мы обсуждали ранее. Допустим, мы хотим удалить и OrderLine из Order, и мы сделаем это простым способом:

orderLineDao.remove(lineToDelete);

Существует проблема с этим кодом. Когда вы говорите менеджеру сущности удалить сущность, она не будет автоматически удалена из любых ассоциаций, которые на нее указывают. Так же, как JPA не управляет автоматически двунаправленными ассоциациями . В этом случае это будет orderLines, установленный в объекте Order, на который указывает свойство OrderLine.order. Если бы я назвал это утверждение неудачным тестовым примером JUnit, это было бы так:

OrderLine orderLineToRemove = orderLineDao.findById(someOrderLineId);
Order parentOrder = orderLineToRemove.getOrder();
int sizeBeforeRemoval = parentOrder.getOrderLines().size();
orderLineDao.remove(orderLineToRemove);
assertEquals(sizeBeforeRemoval - 1, parentOrder.getOrderLines().size());

Последствия

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

  • Любой код, который использует объект Order после того, как мы удалили OrderLine, но все равно увидим этот удаленный OrderLine. Только после совершения транзакции, запуска новой транзакции и перезагрузки ордера в новой транзакции он больше не будет отображаться в наборе Order.orderLines. В простых сценариях мы не столкнемся с этой проблемой, но когда все усложняется, мы можем быть удивлены появлением этих «зомби» OrderLines.
  • Когда операция PERSIST каскадно переходит из класса Order в ассоциацию Order.orderLines, и содержащийся объект Order не удаляется в той же транзакции, мы получим ошибку, такую ​​как «удаленная сущность передана для сохранения» . В отличие от ошибки «отдельная сущность передана для сохранения», о которой мы говорили в предыдущем блоге , эта ошибка вызвана тем, что объект Order имеет ссылку на уже удаленный объект OrderLine. Эта ссылка затем обнаруживается, когда поставщик JPA сбрасывает объекты в контексте постоянства в базу данных, заставляя его пытаться сохранить уже удаленный объект. И, следовательно, ошибка появляется.

Простое решение

Чтобы исправить эту проблему, мы также должны удалить OrderLine из набора Order.orderLines. Это звучит очень знакомо … На самом деле, при управлении двунаправленными ассоциациями мы также должны были убедиться, что обе стороны ассоциации были в согласованном состоянии. А это значит, что мы можем повторно использовать шаблон, который мы использовали там. Добавляя вызов orderLineToRemove.setOrder (null); к тесту это удастся:

OrderLine orderLineToRemove = orderLineDao.findById(someOrderLineId);
Order parentOrder = orderLineToRemove.getOrder();
int sizeBeforeRemoval = parentOrder.getOrderLines().size();
orderLineToRemove.setOrder(null);
orderLineDao.remove(orderLineToRemove);
assertEquals(sizeBeforeRemoval - 1, parentOrder.getOrderLines().size());

Шаблон

Но выполнение этого делает наш код хрупким, так как от пользователей наших доменных объектов зависит правильные методы. DAO должен нести ответственность за это. Очень хороший способ решить эту проблему — использовать хук жизненного цикла сущности @PreRemove следующим образом:

@Entity
public class OrderLine {
[...]

@PreRemove
public void preRemove() {
setOrder(null);
}
}

Теперь мы можем просто вызвать OrderLineDao.remove (), чтобы избавиться от нежелательного объекта OrderLine.

Первоначально в этом блоге предлагалось ввести интерфейс HasPreRemove с методом preRemove, который будет вызываться DAO. Но Сакураба прокомментировал ниже, что аннотация @PreRemove — это то, что нам здесь нужно. Еще раз спасибо, Сакураба!

Удаление сирот

Но что, спросите вы, произошло бы, если бы мы просто удалили OrderLine из набора Order.orderLines следующим образом:

Order parentOrder = orderLineToRemove.getOrder();
parentOrder.removeOrderLine(orderLineToRemove);

?

Действительно, OrderLine будет удален из набора Order.orderLines. И не только в этой транзакции. Если мы снова получим объект Order в новой транзакции, удаленная OrderLine все равно не будет отображаться. Но если бы мы заглянули в базу данных, мы бы увидели, что OrderLine все еще там. Просто в поле OrderLine.order установлено значение null. Здесь мы видим «осиротевший» элемент set. Есть два способа решения этой проблемы:

  • Явно удалите объект OrderLine, как мы обсуждали выше.
  • Если вы используете Hibernate в качестве поставщика JPA, вы можете позволить Hibernate автоматически удалять этих сирот. Добавьте примечание org.hibernate.annotations.Cascade со значением org.hibernate.annotations.CascadeType.DELETE_ORPHAN рядом с @OneToMany, для которого вы хотите, чтобы Hibernate делал это. См. Документацию Hibernate для примера .

Хотя второе решение зависит от поставщика, оно имеет приятную особенность — не требует, чтобы ваш код вызывал метод удаления DAO каждый раз, когда вы удаляете сущность из набора. Но чтобы было очевидно, что вы используете расширение, специфичное для поставщика, вы должны обратиться к этим аннотациям, используя полное имя пакета (как предлагает Java Persistence с Hibernate ):

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
@org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<OrderLine> orderLines = new HashSet<OrderLine>();

Обратите внимание, что CascadeType.ALL не включает каскадный тип DELETE_ORPHAN. Вот почему пример здесь явно устанавливает это.

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

С http://blog.xebia.com