В более старых версиях Hibernate я вижу удаление из одного кадра, указанное в руководстве. Но более новые версии больше не имеют этого раздела. Я не уверен почему. Итак, в этом посте я посмотрю, все ли еще работает.
Раздел удаления из одного кадра говорит:
Удаление элементов коллекции один за другим иногда может быть крайне неэффективным. Hibernate знает, что не следует делать в случае новой пустой коллекции (например, если вы вызвали
list.clear()
). В этом случае Hibernate выдаст одинDELETE
.Предположим, вы добавили один элемент в коллекцию размером двадцать, а затем удалили два элемента. Hibernate выдаст одну
INSERT
и две инструкцииDELETE
, если коллекция не является сумкой. Это, конечно, желательно.Однако предположим, что мы удалили восемнадцать элементов, оставив два, а затем добавили новые элементы. Есть два возможных способа продолжить
- удалить восемнадцать строк одну за другой, а затем вставить три строки
- удалить всю коллекцию в одном SQL
DELETE
и вставить все пять текущих элементов по одномуHibernate не может знать, что второй вариант, вероятно, быстрее. Вероятно, было бы нежелательно, чтобы Hibernate был настолько интуитивным, поскольку такое поведение могло бы сбить с толку триггеры базы данных и т. Д.
К счастью, вы можете вызвать это поведение (то есть вторую стратегию) в любое время, отбросив (т.е. разыменовав) исходную коллекцию и вернув вновь созданную коллекцию со всеми текущими элементами.
Удаление одним выстрелом не применяется к коллекциям, сопоставленным
inverse="true"
.
Значение inverse="true"
предназначено для (Hibernate Mapping) XML. Но в этом посте мы увидим, как «одноразовое удаление» работает в JPA (с Hibernate в качестве поставщика).
Мы попробуем разные подходы и посмотрим, какой из них приведет к удалению одним выстрелом.
- Двунаправленный один-ко-многим
- Однонаправленный один-ко-многим (с соединительным столом)
- Однонаправленный один-ко-многим (без таблицы соединений)
-
ElementCollection
один-ко-многим (используяElementCollection
)
Мы будем использовать сущность Cart
со многими CartItem
s.
Двунаправленный один-ко-многим
Для этого у нас есть ссылки с обеих сторон.
01
02
03
04
05
06
07
08
09
10
|
@Entity public class Cart { ... @OneToMany (mappedBy= "cart" , cascade=ALL, orphanRemoval= true ) Collection<OrderItem> items; } @Entity public class CartItem { ... @ManyToOne Cart cart; } |
Чтобы проверить это, мы вставляем одну строку в таблицу для Cart
и три или более строк в таблицу для CartItem
. Затем мы запускаем тест.
01
02
03
04
05
06
07
08
09
10
11
|
public class CartTests { ... @Test public void testOneShotDelete() throws Exception { Cart cart = entityManager.find(Cart. class , 53L); for (CartItem item : cart.items) { item.cart = null ; // remove reference to cart } cart.items.clear(); // as indicated in Hibernate manual entityManager.flush(); // just so SQL commands can be seen } } |
В показанных командах SQL каждый элемент был удален индивидуально (а не как одноразовое удаление).
1
2
3
|
delete from CartItem where id=? delete from CartItem where id=? delete from CartItem where id=? |
Отбрасывание оригинальной коллекции тоже не сработало. Это даже вызвало исключение.
1
2
3
4
5
6
7
8
9
|
public class CartTests { ... @Test public void testOneShotDelete() throws Exception { Cart cart = entityManager.find(Cart. class , 53L); // remove reference to cart cart.items = new LinkedList<CartItem>(); // discard, and use new collection entityManager.flush(); // just so SQL commands can be seen } } |
1
2
3
|
javax.persistence.PersistenceException: org.hibernate.HibernateException: A collection with cascade= "all-delete-orphan" was no longer referenced by the owning entity instance: ….Cart.items |
Я проверял это с Hibernate 4.3.11 и HSQL 2.3.2. Если ваши результаты отличаются, пожалуйста, нажмите на комментарии .
Однонаправленный «один ко многим» (с таблицей соединений)
Для этого мы вносим изменения в отображение. Это приводит к созданию таблицы соединения.
01
02
03
04
05
06
07
08
09
10
|
@Entity public class Cart { ... @OneToMany (cascade=ALL) Collection<OrderItem> items; } @Entity public class CartItem { ... // no @ManyToOne Cart cart; } |
Опять же, мы вставляем одну строку в таблицу для Cart
и три или более строк в таблицу для CartItem
. Мы также должны вставить соответствующие записи в таблицу соединений ( Cart_CartItem
). Затем мы запускаем тест.
1
2
3
4
5
6
7
8
|
public class CartTests { ... @Test public void testOneShotDelete() throws Exception { Cart cart = entityManager.find(Cart. class , 53L); cart.items.clear(); // as indicated in Hibernate manual entityManager.flush(); // just so SQL commands can be seen } } |
Показанные команды SQL удалили связанные строки в таблице соединений (одной командой). Но строки в таблице для CartItem
все еще существуют (и не удаляются).
1
2
|
delete from Cart_CartItem where cart_id=? // no delete commands for CartItem |
Хм, не совсем то, что мы хотим, так как строки в таблице для CartItem
все еще существуют.
Однонаправленный «один ко многим» (без таблицы соединений)
Начиная с JPA 2.0, таблицу соединений можно избежать в однонаправленном режиме «один ко многим», указав @JoinColumn
.
01
02
03
04
05
06
07
08
09
10
11
|
@Entity public class Cart { ... @OneToMany (cascade=CascadeType.ALL, orphanRemoval= true ) @JoinColumn (name= "cart_id" , updatable= false , nullable= false ) Collection<OrderItem> items; } @Entity public class CartItem { ... // no @ManyToOne Cart cart; } |
Опять же, мы вставляем одну строку в таблицу для Cart
и три или более строк в таблицу для CartItem
. Затем мы запускаем тест.
1
2
3
4
5
6
7
8
|
public class CartTests { ... @Test public void testOneShotDelete() throws Exception { Cart cart = entityManager.find(Cart. class , 53L); cart.items.clear(); // as indicated in Hibernate manual entityManager.flush(); // just so SQL commands can be seen } } |
Отказ от оригинальной коллекции тоже не сработал. Это также вызвало то же исключение (как и в случае двунаправленного «один ко многим»).
1
2
3
|
javax.persistence.PersistenceException: org.hibernate.HibernateException: A collection with cascade= "all-delete-orphan" was no longer referenced by the owning entity instance: ….Cart.items |
ElementCollection
One-to-Many (с ElementCollection
)
JPA 2.0 представила @ElementCollection
. Это позволяет устанавливать отношения один-ко-многим, причем многие стороны являются либо @Basic
либо @Embeddable
(т.е. не @Entity
).
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@Entity public class Cart { ... @ElementCollection // @OneToMany for basic and embeddables @CollectionTable (name= "CartItem" ) // defaults to "Cart_items" if not overridden Collection<OrderItem> items; } @Embeddable // not an entity! public class CartItem { // no @Id // no @ManyToOne Cart cart; private String data; // just so that there are columns we can set } |
Опять же, мы вставляем одну строку в таблицу для Cart
и три или более строк в таблицу для CartItem
. Затем мы запускаем тест.
1
2
3
4
5
6
7
8
|
public class CartTests { ... @Test public void testOneShotDelete() throws Exception { Cart cart = entityManager.find(Cart. class , 53L); cart.items.clear(); // as indicated in Hibernate manual entityManager.flush(); // just so SQL commands can be seen } } |
Yey! Связанные строки для CartItem
были удалены за один раз.
1
|
delete from CartItem where Cart_id=? |
Заключительные мысли
Удаление одним выстрелом происходит с однонаправленным «один ко многим» с использованием ElementCollection
(где сторона с множеством сторон является вложенной, а не сущностью).
В сценарии однонаправленной связи «один ко многим» с таблицей соединений удаление записей в таблице соединений не приносит особой пользы.
Я не уверен, почему удаление одним выстрелом работает (или почему это работает таким образом) в Hibernate. Но у меня есть предположение. И это означает, что базовый поставщик JPA не может выполнить однократное удаление, поскольку он не может гарантировать, что на объект многих сторон не будут ссылаться другие объекты. В отличие от ElementCollection
, ElementCollection
не является сущностью и на нее не могут ссылаться другие сущности.
Теперь это не означает, что вы должны использовать ElementCollection
все время. Возможно, удаление одним выстрелом относится только к совокупным корням. В этих случаях использование Embeddable
и ElementCollection
может быть подходящим для коллекции объектов-значений, которые составляют агрегат. Когда удален совокупный корень, было бы хорошо видеть, что «дочерние» объекты также должны быть удалены (и эффективным способом).
Мне бы хотелось, чтобы в JPA был способ указать, что дочерние сущности находятся в частной собственности и могут быть безопасно удалены при удалении родительской сущности (например, аналогично @PrivateOwned
в EclipseLink). Посмотрим, будет ли он включен в будущую версию API.
Надеюсь это поможет.
Ссылка: | Удаление одним выстрелом с помощью Hibernate (JPA) от нашего партнера по JCG Лоренцо Ди из блога « Адаптация и обучение» . |