«1. Обзор

В этом руководстве мы обсудим разницу между двумя вариантами удаления сущностей из наших баз данных при работе с JPA.

Во-первых, мы начнем с CascadeType.REMOVE, который представляет собой способ удаления дочерней сущности или сущностей, когда происходит удаление ее родителя. Затем мы рассмотрим атрибут orphanRemoval, который был представлен в JPA 2.0. Это дает нам возможность удалять потерянные объекты из базы данных.

На протяжении всего руководства мы будем использовать простой домен интернет-магазина для демонстрации наших примеров.

2. Модель домена

Как упоминалось ранее, в этой статье используется простой домен интернет-магазина. При этом OrderRequest имеет ShipmentInfo и список LineItem.

Учитывая это, давайте рассмотрим:

    Для удаления ShipmentInfo, когда происходит удаление OrderRequest, мы будем использовать CascadeType.REMOVE Для удаления LineItem из OrderRequest мы будем использовать orphanRemoval ~~ ~ Во-первых, давайте создадим объект ShipmentInfo:

Затем давайте создадим объект LineItem:

@Entity
public class ShipmentInfo {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    // constructors
}

Наконец, давайте соберем все вместе, создав объект OrderRequest:

@Entity
public class LineItem {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    @ManyToOne
    private OrderRequest orderRequest;

    // constructors, equals, hashCode
}

Стоит выделение метода removeLineItem, который отсоединяет LineItem от 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);
    }
}

3. CascadeType.REMOVE

Как было сказано ранее, пометка поля ссылки с помощью CascadeType.REMOVE — это способ удалить дочерний объект или объекты всякий раз, когда происходит удаление его родителя.

В нашем случае OrderRequest имеет ShipmentInfo, у которого есть CascadeType.REMOVE.

Чтобы проверить удаление ShipmentInfo из базы данных при удалении OrderRequest, давайте создадим простой интеграционный тест:

Из утверждений видно, что удаление 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());
}

4. orphanRemoval

Как было сказано ранее, он используется для удаления потерянных объектов из базы данных. Сущность, которая больше не привязана к своему родителю, определяется как сирота.

В нашем случае OrderRequest имеет коллекцию объектов LineItem, где мы используем аннотацию @OneToMany для определения отношения. Здесь мы также устанавливаем для атрибута orphanRemoval значение true. Чтобы отсоединить LineItem от OrderRequest, мы можем использовать ранее созданный метод removeLineItem.

Когда все на месте, как только мы воспользуемся методом removeLineItem и сохраним OrderRequest, должно произойти удаление потерянного LineItem из базы данных.

Чтобы проверить удаление потерянного элемента 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());
}

Кроме того, стоит отметить, что метод removeLineItem изменяет список LineItem, а не переназначает ему значение. Выполнение последнего приведет к PersistenceException.

Чтобы проверить заявленное поведение, давайте создадим окончательный интеграционный тест:

5. Заключение

@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();
}

В этой статье мы рассмотрели разницу между CascadeType.REMOVE и orphanRemoval, используя простой домен интернет-магазина. . Кроме того, чтобы убедиться, что объекты были правильно удалены из нашей базы данных, мы создали несколько интеграционных тестов.

Как всегда, полный исходный код статьи доступен на GitHub.

«