JPA 中的 CascadeType.REMOVE 和 orphanRemoval 有啥区别?

Posted

技术标签:

【中文标题】JPA 中的 CascadeType.REMOVE 和 orphanRemoval 有啥区别?【英文标题】:What is the difference between CascadeType.REMOVE and orphanRemoval in JPA?JPA 中的 CascadeType.REMOVE 和 orphanRemoval 有什么区别? 【发布时间】:2013-09-19 17:40:47 【问题描述】:

有什么区别

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

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

这个例子来自Java EE Tutorial,但是我还是不明白。

【问题讨论】:

孤儿移除意味着当与其“父”实体的关系被破坏时,依赖实体被移除。 写了一个test case 来说明这个概念。 【参考方案1】:

来自here:-

级联删除

使用 CascadeType.REMOVE(或 CascadeType.ALL, 其中包括 REMOVE) 表示删除操作应该是 自动级联到它所引用的实体对象 字段(一个集合可以引用多个实体对象 字段):

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

孤儿删除

JPA 2 支持额外且更激进的删除级联模式 可以使用 orphanRemoval 元素指定 @OneToOne 和 @OneToMany 注释:

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

区别:-

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

如果指定了 orphanRemoval=true,断开连接的 Address 实例将被自动删除。这对清理很有用 不应该存在的依赖对象(例如地址) 来自所有者对象(例如 Employee)的引用。 如果仅指定 cascade=CascadeType.REMOVE,则不会执行自动操作,因为断开关系不是删除 操作。

【讨论】:

【参考方案2】:

了解CascadeType.REMOVEorphanRemoval=true 之间区别的简单方法。

对于孤儿移除: 如果你调用setOrders(null),相关的Order实体会自动从db中移除。

对于移除级联: 如果您调用setOrders(null),相关的Order 实体将不会自动从数据库中删除。

【讨论】:

删除 === 删除【参考方案3】:

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

@Entity
class parent 
  //id and other fields
 @OneToMany (orphanRemoval = "true",cascade = CascadeType.REMOVE)
   Set<Person> myChildern;

orphanRemoval 是一个 ORM 概念,它告诉孩子是否是孤儿。它也应该从数据库中删除。

当无法从其父级访问子级时,该子级将成为孤儿。 例如,如果我们删除 Person 对象集(将其设置为空集)或将其替换为新集,则父级无法再访问旧集中的子级,而子级将成为孤儿,因此子级注定要成为也从数据库中删除。

CascadeType.REMOVE 是一个数据库级别的概念,它告诉如果父表被删除,它在子表中的所有相关记录都应该被删除。

【讨论】:

这个答案似乎更合乎逻辑,然后是这个线程上的任何其他答案。【参考方案4】:

实际上区别在于您是尝试更新数据(PATCH)还是完全替换数据(PUT)

假设您删除了customer,而不是使用cascade=REMOVE 也会删除看似有意和有用的客户订单。

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

现在假设您将customer 更新为orphanRemoval="true",它将删除所有以前的订单并用提供的订单替换它们。 (PUT 换成REST API

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

如果没有orphanRemoval,旧订单将被保留。 (PATCH 换成REST API

【讨论】:

【参考方案5】:

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

PostComment 子实体已被删除,因为 CascadeType.REMOVE 策略就像我们也删除了子实体一样。

孤儿移除策略

孤儿移除策略,需要通过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 语句:

DELETE FROM post_comment 
WHERE id = 2

【讨论】:

以上是关于JPA 中的 CascadeType.REMOVE 和 orphanRemoval 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

JPA 单向一对多关联关系

hibernate CascadeType属性

如何使用连接列指定外键?

springboot实体类的级联说明

Hibernate 从 onetomany 中删除

列名中的 JPA '.'(点)