1. 概述
在使用 JPA 进行数据库操作时,我们有两种常见的删除实体的方式:CascadeType.REMOVE
和 orphanRemoval
。虽然它们都用于删除子实体,但适用场景和行为机制却有所不同。
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()
方法用于将 LineItem
从 OrderRequest
中移除,是触发 orphanRemoval
的关键操作。
3. CascadeType.REMOVE 的使用
CascadeType.REMOVE
的作用是:当父实体被删除时,自动删除其关联的子实体。
在我们的例子中,OrderRequest
与 ShipmentInfo
是一对一关系,并配置了 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
的作用是:当某个子实体从父实体的集合中被移除时,自动将其从数据库中删除。
在我们的例子中,OrderRequest
与 LineItem
是一对多关系,并启用了 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 项目 中找到。