1. Обзор
В этом руководстве мы обсудим разницу между двумя вариантами удаления сущностей из наших баз данных при работе с JPA .
Во- первых, мы начнем с CascadeType.REMOVE
, который представляет собой способ удаления дочернего объекта или объектов, когда происходит удаление его родителя . Затем мы рассмотрим атрибут orphanRemoval
, который был введен в JPA 2.0. Это дает нам возможность удалять потерянные объекты из базы данных .
На протяжении всего руководства мы будем использовать простой домен интернет-магазина для демонстрации наших примеров.
2. Модель предметной области
Как упоминалось ранее, в этой статье используется простой домен интернет-магазина. При этом OrderRequest
имеет ShipmentInfo
и список LineItem
.
Учитывая это, рассмотрим:
- Для удаления
ShipmentInfo,
когда происходит удалениеOrderRequest
, мы будем использоватьCascadeType.REMOVE.
- Для удаления
LineItem
изOrderRequest
мы будем использоватьorphanRemoval
Во-первых, давайте создадим сущность `ShipmentInfo :`
@Entity
public class ShipmentInfo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// constructors
}
Далее создадим сущность LineItem :
@Entity
public class LineItem {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
@ManyToOne
private OrderRequest orderRequest;
// constructors, equals, hashCode
}
Наконец, давайте соберем все вместе, создав объект OrderRequest
:
@Entity
public class OrderRequest {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToOne(cascade = { CascadeType.REMOVE, CascadeType.PERSIST })
private ShipmentInfo shipmentInfo;
@OneToMany(orphanRemoval = true, cascade = CascadeType.PERSIST, mappedBy = "orderRequest")
private List<LineItem> lineItems;
// constructors
public void removeLineItem(LineItem lineItem) {
lineItems.remove(lineItem);
}
}
Стоит выделить метод removeLineItem
, который отделяет LineItem
от OrderRequest
.
3. CascadeType.УДАЛИТЬ
Как указывалось ранее, пометка поля ссылки с помощью CascadeType.REMOVE
— это способ удалить дочернюю сущность или сущности всякий раз, когда происходит удаление их родителя .
В нашем случае OrderRequest
имеет ShipmentInfo
, у которого есть CascadeType.REMOVE
.
Чтобы проверить удаление ShipmentInfo
из базы данных при удалении OrderRequest
, давайте создадим простой интеграционный тест:
@Test
public void whenOrderRequestIsDeleted_thenDeleteShipmentInfo() {
createOrderRequestWithShipmentInfo();
OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);
entityManager.getTransaction().begin();
entityManager.remove(orderRequest);
entityManager.getTransaction().commit();
Assert.assertEquals(0, findAllOrderRequest().size());
Assert.assertEquals(0, findAllShipmentInfo().size());
}
private void createOrderRequestWithShipmentInfo() {
ShipmentInfo shipmentInfo = new ShipmentInfo("name");
OrderRequest orderRequest = new OrderRequest(shipmentInfo);
entityManager.getTransaction().begin();
entityManager.persist(orderRequest);
entityManager.getTransaction().commit();
Assert.assertEquals(1, findAllOrderRequest().size());
Assert.assertEquals(1, findAllShipmentInfo().size());
}
Из утверждений мы видим, что удаление OrderRequest также
привело к успешному удалению связанной с ним информации о доставке
.
4. удаление сирот
Как указывалось ранее, он используется для удаления потерянных сущностей из базы данных . Сущность, которая больше не привязана к своему родителю, определяется как сирота .
В нашем случае OrderRequest
имеет коллекцию объектов LineItem
, где мы используем аннотацию @OneToMany
для идентификации отношения .
Здесь мы также устанавливаем для атрибута orphanRemoval значение
true
. Чтобы отсоединить LineItem
от OrderRequest
, мы можем использовать ранее созданный метод removeLineItem
.
Когда все на месте, как только мы воспользуемся методом removeLineItem
и сохраним OrderRequest
, должно произойти удаление потерянного LineItem
из базы данных.
Чтобы проверить удаление потерянного LineItem
из базы данных, давайте создадим еще один интеграционный тест:
@Test
public void whenLineItemIsRemovedFromOrderRequest_thenDeleteOrphanedLineItem() {
createOrderRequestWithLineItems();
OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);
LineItem lineItem = entityManager.find(LineItem.class, 2L);
orderRequest.removeLineItem(lineItem);
entityManager.getTransaction().begin();
entityManager.merge(orderRequest);
entityManager.getTransaction().commit();
Assert.assertEquals(1, findAllOrderRequest().size());
Assert.assertEquals(2, findAllLineItem().size());
}
private void createOrderRequestWithLineItems() {
List<LineItem> lineItems = new ArrayList<>();
lineItems.add(new LineItem("line item 1"));
lineItems.add(new LineItem("line item 2"));
lineItems.add(new LineItem("line item 3"));
OrderRequest orderRequest = new OrderRequest(lineItems);
entityManager.getTransaction().begin();
entityManager.persist(orderRequest);
entityManager.getTransaction().commit();
Assert.assertEquals(1, findAllOrderRequest().size());
Assert.assertEquals(3, findAllLineItem().size());
}
Опять же, из утверждений видно, что мы успешно удалили потерянный LineItem
из базы данных.
Кроме того, стоит упомянуть, что метод removeLineItem
изменяет список LineItem
вместо того, чтобы переназначать ему значение. Выполнение последнего приведет к PersistenceException
.
Чтобы проверить заявленное поведение, давайте создадим окончательный интеграционный тест:
@Test(expected = PersistenceException.class)
public void whenLineItemsIsReassigned_thenThrowAnException() {
createOrderRequestWithLineItems();
OrderRequest orderRequest = entityManager.find(OrderRequest.class, 1L);
orderRequest.setLineItems(new ArrayList<>());
entityManager.getTransaction().begin();
entityManager.merge(orderRequest);
entityManager.getTransaction().commit();
}
5. Вывод
В этой статье мы рассмотрели разницу между CascadeType.REMOVE
и orphanRemoval
, используя простой домен интернет-магазина. Кроме того, чтобы убедиться, что объекты были правильно удалены из нашей базы данных, мы создали несколько интеграционных тестов.
Как всегда, полный исходный код статьи доступен на GitHub .