1. 概述

在使用 JPA 进行数据库操作时,我们有两种常见的删除实体的方式:CascadeType.REMOVEorphanRemoval。虽然它们都用于删除子实体,但适用场景和行为机制却有所不同。

  • CascadeType.REMOVE:当父实体被删除时,自动删除关联的子实体。
  • orphanRemoval:当某个子实体从父实体中移除(即变为“孤儿”)时,自动将其从数据库中删除。

本文将通过一个简单的在线商城模型,来演示这两个机制的具体使用方式与差异。

2. 领域模型设计

我们使用一个典型的在线商城场景:

  • 一个 OrderRequest 包含一个 ShipmentInfo(配送信息)和多个 LineItem(订单项)。
  • 删除 OrderRequest 时,同时删除其 ShipmentInfo → 使用 CascadeType.REMOVE
  • OrderRequest 中移除某个 LineItem 时,自动删除该 LineItem → 使用 orphanRemoval

2.1 定义 ShipmentInfo 实体

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

    private String name;

    // constructors
}

2.2 定义 LineItem 实体

@Entity
public class LineItem {

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

    private String name;

    @ManyToOne
    private OrderRequest orderRequest;

    // constructors, equals, hashCode
}

2.3 定义 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() 方法用于将 LineItemOrderRequest 中移除,是触发 orphanRemoval 的关键操作。

3. CascadeType.REMOVE 的使用

CascadeType.REMOVE 的作用是:当父实体被删除时,自动删除其关联的子实体

在我们的例子中,OrderRequestShipmentInfo 是一对一关系,并配置了 CascadeType.REMOVE。因此,当我们删除 OrderRequest 时,JPA 会自动删除其关联的 ShipmentInfo

3.1 验证删除行为的测试代码

@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 的使用

orphanRemoval = true 的作用是:当某个子实体从父实体的集合中被移除时,自动将其从数据库中删除

在我们的例子中,OrderRequestLineItem 是一对多关系,并启用了 orphanRemoval = true。当我们调用 removeLineItem() 方法将某个 LineItem 从集合中移除并保存 OrderRequest 后,该 LineItem 就会被自动删除。

4.1 验证 orphanRemoval 删除行为的测试代码

@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 被自动删除。

⚠️ 踩坑提醒:如果通过重新赋值集合(如 setLineItems(new ArrayList<>()))来“清空”子实体,JPA 无法识别这种“移除”行为,可能会导致异常。

4.2 错误用法示例

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

❌ 该测试会抛出 PersistenceException,说明直接替换集合是不可取的做法。

5. 总结

特性 CascadeType.REMOVE orphanRemoval
触发时机 删除父实体时 从父实体集合中移除子实体时
适用关系 一对一 / 一对多 一对多 / 多对多
配置方式 cascade = CascadeType.REMOVE orphanRemoval = true
实际用途 级联删除 自动清理“孤儿”实体

📌 简单粗暴总结

  • 删除父实体时想顺带删子实体 → ✅ 用 CascadeType.REMOVE
  • 从父实体中移除子实体时想自动删掉它 → ✅ 用 orphanRemoval = true

本文所有示例代码均可在 GitHub 项目 中找到。


原始标题:JPA CascadeType.REMOVE vs orphanRemoval | Baeldung