JPA orphanRemoval = true与ON DELETE CASCADE DML子句有何不同


184

我对JPA 2.0 orphanRemoval属性有些困惑。

我想我可以看到,当我使用JPA提供程序的数据库生成工具来创建基础数据库DDL时,需要具有ON DELETE CASCADE特定的关系。

但是,如果数据库存在并且已经具有ON DELETE CASCADEon关系,这是否不足以适当地级联删除?orphanRemoval另外还做什么?

干杯

Answers:


291

orphanRemoval与无关ON DELETE CASCADE

orphanRemoval是一个完全ORM特定的东西。当不再从“父”实体中引用“子”实体时,例如,当您从父实体的相应集合中删除子实体时,它会将“子”实体标记为要删除。

ON DELETE CASCADE特定数据库的事物,当删除“父”行时,它将删除数据库中的“子”行。


3
这是否意味着它们具有安全效果,但是由其他系统负责实现此效果?
Anonymoose 2012年

101
匿名,它没有相同的效果。删除父级时,ON DELETE CASCADE通知DB删除所有子级记录。那就是如果我删除发票,然后删除该发票上的所有ITEMS。OrphanRemoval告诉ORM,如果我从属于发票对象的项目集合中删除一个Item对象(在内存操作中),然后“保存”该发票,则应该从基础数据库中删除已删除的项目。
garyKeorkunian

2
如果您使用的单向关系,那么孤儿将被自动删除,即使您没有设置orphanRemoval =真
蒂姆

98

此处采用的示例:

当一个Employee实体对象被移除,删除操作被级联到引用的Address实体对象。在这方面,orphanRemoval=truecascade=CascadeType.REMOVE相同,如果orphanRemoval=true指定,CascadeType.REMOVE则是多余的。

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

  • 如果orphanRemoval=true指定为,则断开连接的Address实例将自动删除。这对于清理Address没有所有者对象(例如Employee)的引用不应该存在的依赖对象(例如)很有用。

  • 如果仅cascade=CascadeType.REMOVE指定,则不执行任何自动操作,因为断开关系不是删除操作。

为避免由于孤立删除而导致引用悬而未决,应仅对包含私有非共享依赖对象的字段启用此功能。

我希望这一点更加清楚。


阅读您的答案后,我意识到他们之间的确切区别,并且我的问题已解决。如果将子实体与父实体中定义的集合断开(删除),则我将无法从数据库中删除子实体。同样,我问了一个问题“ stackoverflow.com/questions/15526440/… ”。只需添加我的评论即可链接这两个问题。
Narendra Verma


46

从集合中删除子实体之后,您还将同时从数据库中删除该子实体。orphanRemoval还意味着您无法更改父母;如果有一个部门有员工,那么一旦您将该员工删除以放入另一个部门,您将无意中在刷新/提交时将该员工从数据库中删除(以先到者为准)。士气是将orphanRemoval设置为true,只要您确定该父级的子级在整个存在期间都不会迁移到另一个父级即可。启用orphanRemoval也会自动将REMOVE添加到级联列表。


3
完全正确...也称为“私人”父母/子女关系。
HDave

这意味着,一旦我打电话叫department.remove(emp);该员工,便会从emp表中删除该员工,甚至都不会打电话commit()
JavaTechnical 2014年

18

用于DDL等效JPA映射ON DELETE CASCADEcascade=CascadeType.REMOVE。孤立删除意味着从属实体在与“父”实体的关系被破坏时也被删除。例如,如果从@OneToMany关系中删除了一个孩子却没有在实体管理器中显式删除它。


1
cascade=CascadeType.REMOVE不等于ON DELETE CASCADE。启用时,请删除应用程序代码,并且不会影响DDL,其他会在DB中执行。参见stackoverflow.com/a/19696859/548473
Grigory Kislin,

9

区别在于:
-orphanRemoval = true:当不再引用“子”实体时,“子”实体将被删除(其父对象可能不会被删除)。
-CascadeType.REMOVE:仅在删除其“父项”时,才删除“子级”实体。


6

由于这是一个非常常见的问题,因此我写了这篇文章,此答案基于该文章

实体状态转换

JPA将实体状态转换转换为SQL语句,例如INSERT,UPDATE或DELETE。

JPA实体状态转换

当您persist是实体时,您计划将INSERT语句安排在EntityManager刷新时自动或手动执行。

当您remove是实体时,您正在计划DELETE语句,该语句将在刷新持久性上下文时执行。

级联实体状态转换

为了方便起见,JPA允许您传播从父实体到子实体的实体状态转换。

因此,如果您有一个与Post子实体有@OneToMany关联的父PostComment实体:

Post and PostComment实体

实体中的comments集合Post映射如下:

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

CascadeType.ALL

cascade属性告诉JPA提供者将实体状态从父Post实体传递到集合中PostComment包含的所有实体comments

因此,如果您删除Post实体:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

JPA提供程序将首先删除该PostComment实体,并且在删除所有子实体时,它也会同时删除该Post实体:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

移除孤儿

当您将orphanRemoval属性设置true为时,remove当从集合中删除子实体时,JPA提供程序将安排操作。

因此,就我们而言

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

JPA提供者将删除关联的post_comment记录,因为PostCommentcomments集合中不再引用该实体:

DELETE FROM post_comment WHERE id = 1

删除级联

ON DELETE CASCADE是在FK级别定义:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

完成后,如果您删除post一行:

DELETE FROM post WHERE id = 1

post_comment数据库引擎会自动删除所有关联的实体。但是,如果您误删除了根实体,这将是非常危险的操作。

结论

JPA cascadeorphanRemoval选件的优点在于,您还可以从乐观锁定中受益,以防止更新丢失

如果您使用JPA级联机制,则无需使用DDL级ON DELETE CASCADE,如果删除具有多个子级实体的根实体,这将是非常危险的操作。


因此,在“孤立删除”部分中,您的答案是:post.getComments()。remove(postComment); 仅因为Persist级联,才可以在OneToMany双向映射中工作。如您的示例所示,如果没有级联并且没有在ManyToOne端进行删除,则无法在DB中持久删除2个实体之间的连接?
aurelije

移除孤儿不受的影响CascadeType。这是一种补充机制。现在,您误删除了持久性。孤儿删除是关于删除未引用的关联,而持久化是关于保存新实体。您需要按照答案中提供的链接来更好地理解这些概念。
Vlad Mihalcea

我不明白一件事:如果我们从不移除M侧的连接,那么孤儿移除将如何在双向映射中起作用?我认为从Post列表中删除PostComment而不将PostComment.post设置为null不会导致删除数据库中这两个实体之间的连接。这就是为什么我认为无法消除孤儿的原因,在关系世界中,PostComment不是孤儿。有空的时候我会测试一下。
aurelije

1
在高性能Java持久性GitHub存储库中添加了这两个示例,它们演示了它们如何工作。您不需要像通常直接删除实体那样同步子端。但是,仅在添加级联的情况下,孤立删除才有效,但这似乎是Hibernate的限制,而不是JPA规范。
Vlad Mihalcea

5

@GaryK的回答绝对很棒,我花了一个小时来寻找orphanRemoval = truevs 的解释CascadeType.REMOVE,它有助于我理解。

总结:仅当我们删除对象()并且希望也删除子对象时,其orphanRemoval = true工作原理相同。CascadeType.REMOVE entityManager.delete(object)

在完全不同的情况下,当我们使用获取类似的数据List<Child> childs = object.getChilds()然后删除child(entityManager.remove(childs.get(0))时,orphanRemoval=true将导致childs.get(0)与之相对应的实体将从数据库中删除。


1
您在第二段中有一个错字:没有诸如objectManager.delete(obj);这样的方法。它是entityManager.remove(obj)。
JL_SO

3

在以下情况下,孤儿删除的效果与“ ON DELETE CASCADE”相同:-假设我们在学生实体和指导实体之间存在简单的多对一关系,其中许多学生可以映射到同一指南,并且在数据库中有一个学生表和向导表之间的外键关系,以使学生表的id_guide为FK。

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
     @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

    @ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

//父实体

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

在这种情况下,这种关系使得学生实体是关系的所有者,因此我们需要保存该学生实体以持久保存整个对象图,例如

    Guide guide = new Guide("John", "$1500");
    Student s1 = new Student(guide, "Roy","ECE");
    Student s2 = new Student(guide, "Nick", "ECE");
    em.persist(s1);
    em.persist(s2);

在这里,我们将同一指南与两个不同的学生对象映射,并且由于使用了CASCADE.PERSIST,因此对象图将按以下方式保存在数据库表中(在我的情况下为MySql)

学生表:-

ID名称部门Id_Guide

1罗伊ECE 1

2尼克ECE 1

指南表:-

ID NAME薪水

1约翰$ 1500

现在,如果我想删除其中一名学生,请使用

      Student student1 = em.find(Student.class,1);
      em.remove(student1);

当删除学生记录时,也应删除相应的指南记录,这就是Student实体中CASCADE.REMOVE属性出现的位置,它的作用是;它将删除标识符为1的学生以及相应的指南对象(标识符1)。但是在此示例中,还有一个学生对象被映射到同一指南记录,并且除非我们在指南实体中使用orphanRemoval = true属性,否则上面的删除代码将不起作用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.