JPA 中 CascadeType.REMOVE 和 orphanRemoval 有什么区别?

137

什么是区别?

@OneToMany(cascade=REMOVE, mappedBy="customer")
public List<Order> getOrders() { ... }

@OneToMany(mappedBy="customer", orphanRemoval="true")
public List<Order> getOrders() { ... }

这个例子来源于Java EE教程,但我仍然不明白细节。


孤儿删除是指在与其“父”实体的关系被销毁时,依赖实体也会被删除。 - Rahul Tripathi
1
编写了一个测试用例,可以说明这个概念。 - Martin Andersson
5个回答

172

来自这里

级联删除

将一个引用字段标记为CascadeType.REMOVE(或包含REMOVE的CascadeType.ALL),表示删除操作应该自动级联到被该字段引用的实体对象上(多个实体对象可以被一个集合字段引用):

@Entity
class Employee {
     :
    @OneToOne(cascade=CascadeType.REMOVE)
    private Address address;
     :
}

孤儿对象的移除

JPA 2支持一种更加积极的级联删除模式,可以使用@OneToOne和@OneToMany注解中的orphanRemoval元素指定:

@Entity
class Employee {
     :
    @OneToOne(orphanRemoval=true)
    private Address address;
     :
}

区别:

这两个设置的区别在于对关系断开的响应。例如,当将地址字段设置为null或另一个地址对象时。

  • 如果指定了orphanRemoval=true,则自动删除已断开的地址实例。这对于清理依赖对象(例如Address),这些对象没有来自所有者对象(例如Employee)的引用而不应存在非常有用。
  • 如果仅指定cascade=CascadeType.REMOVE,则不会自动执行任何操作,因为断开关系并不是移除操作。

114
理解CascadeType.REMOVE和orphanRemoval=true之间的区别非常简单。
对于孤儿删除(orphan removal): 如果您调用setOrders(null),相关的Order实体将自动从数据库中删除。
对于级联删除(remove cascade): 如果您调用setOrders(null),相关的Order实体不会自动从数据库中删除。

4
移除 === 删除 - Abdull

28

假设我们有一个子实体和一个父实体。一个父亲可以有几个孩子。

@Entity
class parent {
  //id and other fields
 @OneToMany (orphanRemoval = "true",cascade = CascadeType.REMOVE)
   Set<Person> myChildern;
}
< p >orphanRemoval是ORM概念,它表示如果子对象成为孤儿,则应从数据库中删除。

当子对象无法从其父对象访问时,将被视为孤儿。例如,如果我们删除Person对象集(将其设置为空集)或用新集替换它,则父对象将无法访问旧集合中的子对象,这些子对象将成为孤儿,因此将从数据库中删除这些孤儿记录。

CascadeType.REMOVE是一个数据库级别的概念,它表示如果父对象被删除,则应删除其关联的所有子表记录。


1
这个答案似乎比这个帖子上的其他答案更合乎逻辑。 - Jignesh M. Khatri

16

CascadeType.REMOVE

CascadeType.REMOVE 级联策略,可以显式地进行配置:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.REMOVE
)
private List<PostComment> comments = new ArrayList<>();

或者隐式地从CascadeType.ALL策略中继承:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL
)
private List<PostComment> comments = new ArrayList<>();

允许您将从父实体到其子实体的 remove 操作进行传播。

因此,如果我们获取父 Post 实体以及其 comments 集合,并删除 post 实体:

Post post = entityManager.createQuery("""
    select p
    from Post p
    join fetch p.comments
    where p.id = :id
    """, Post.class)
.setParameter("id", postId)
.getSingleResult();

entityManager.remove(post);

Hibernate将执行三个删除语句:

DELETE FROM post_comment 
WHERE id = 2

DELETE FROM post_comment 
WHERE id = 3

DELETE FROM post 
WHERE id = 1

由于使用了CascadeType.REMOVE策略,PostComment子实体也被删除了,就好像我们同时移除了子实体。

孤儿删除策略

需要通过orphanRemoval属性设置的孤儿删除策略:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

允许您在从集合中删除子实体时删除子表行。

因此,如果我们加载Post实体以及其comments集合,并从comments集合中删除第一个PostComment

Post post = entityManager.createQuery("""
    select p
    from Post p
    join fetch p.comments c
    where p.id = :id
    order by p.id, c.id
    """, Post.class)
.setParameter("id", postId)
.getSingleResult();

post.remove(post.getComments().get(0));
Hibernate将执行一个删除语句来删除关联的post_comment表行:
DELETE FROM post_comment 
WHERE id = 2

5

实际上,差异在于您是尝试更新数据(PATCH)还是完全替换数据(PUT)。

假设您删除了customer,那么使用cascade=REMOVE将同时删除该客户的订单,这似乎是有意和有用的。

@OneToMany(cascade=REMOVE, mappedBy="customer")
public List<Order> getOrders() { ... }

现在假设您使用orphanRemoval="true"更新一个customer,它将删除所有先前的订单并用提供的订单替换它们。(在REST API方面使用PUT

@OneToMany(mappedBy="customer", orphanRemoval="true")
public List<Order> getOrders() { ... }

没有使用orphanRemoval,旧订单将被保留。(在REST API的术语中为PATCH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接