1. 概要

このチュートリアルでは、JPAを使用するときにデータベースからエンティティを削除するための2つのオプションの違いについて説明します。

まず、 CascadeType.REMOVE から始めます。これは、親の削除が発生したときに子エンティティを削除する方法です。 次に、JPA2.0で導入されたorphanRemoval属性を見ていきます。 これにより、データベースから孤立したエンティティを削除する方法が提供されます。

チュートリアル全体を通して、簡単なオンラインストアドメインを使用して例を示します。

2. ドメインモデル

前述のように、この記事では単純なオンラインストアドメインを利用しています。 ここで オーダーリクエスト があります ShipmentInfo とのリスト ラインアイテム.

それを踏まえて、考えてみましょう:

  • の除去のために ShipmentInfo、 の削除時 オーダーリクエスト 発生します、使用します CascadeType.REMOVE
  • の除去のために ラインアイテム から オーダーリクエスト、使用します orphanRemoval

まず、ShipmentInfoを作成しましょう 実在物:

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

    private String name;

    // constructors
}

次に、を作成しましょう ラインアイテム 実在物:

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

LineItemOrderRequestから切り離すremoveLineItemメソッドを強調する価値があります。

3.  CascadeType.REMOVE

前述のように、 CascadeType.REMOVE で参照フィールドをマークすることは、を削除する方法です。 1つまたは複数の子エンティティその親の削除が発生するたびに

この場合、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 を削除すると、関連するShipmentInfoも正常に削除されたことがわかります。

4.  orphanRemoval

前に述べたように、その使用法は削除することです孤立したエンティティデータベースから。 親に接続されなくなったエンティティは、 孤児 。 

私たちの場合、 オーダーリクエストのコレクションがありますラインアイテムどこのオブジェクトを使用します @OneToMany 関係を識別するための注釈ここで、orphanRemoval属性もtrueに設定します。 LineItemをOrderRequestからデタッチするには、前に作成したremoveLineItemメソッドを使用できます。

すべてが整ったら、removeLineItemメソッドを使用してOrderRequestを保存すると、孤立した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.REMOVEorphanRemovalの違いについて説明しました。 また、エンティティがデータベースから正しく削除されたことを確認するために、いくつかの統合テストを作成しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。