Перейти к основному содержимому

JPA CascadeType.REMOVE против orphanRemoval

· 4 мин. чтения

Упражнение: Сложение двух чисел

Даны два неотрицательный целых числа в виде непустых связных списков. Их цифры хранятся в обратном порядке. И каждый елемент списка содержить ровно одну цифру. Сложите эти два числа и верните сумму в виде связного списка ...

ANDROMEDA

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 .