orphanRemoval
属性。我认为当使用JPA提供程序的DB生成工具创建基础数据库DDL时需要它,以在特定关系上具有ON DELETE CASCADE
。但是,如果该数据库已经存在并且该关系上已经具有ON DELETE CASCADE
,那么这不足以适当地级联删除吗?orphanRemoval
除了以上操作之外还有什么作用呢?orphanRemoval
属性。我认为当使用JPA提供程序的DB生成工具创建基础数据库DDL时需要它,以在特定关系上具有ON DELETE CASCADE
。但是,如果该数据库已经存在并且该关系上已经具有ON DELETE CASCADE
,那么这不足以适当地级联删除吗?orphanRemoval
除了以上操作之外还有什么作用呢?orphanRemoval
与ON DELETE CASCADE
没有任何关系。
orphanRemoval
是完全与ORM有关的事情。它标记“子”实体在不再从“父”实体引用时将被删除,例如当您从父实体的相应集合中删除子实体时。
ON DELETE CASCADE
是与数据库有关的事情,它在删除“父”行时从数据库中删除“子”行。
Employee
实体对象被删除时,删除操作会级联到引用的Address
实体对象。在这方面,orphanRemoval=true
和cascade=CascadeType.REMOVE
是相同的,如果指定了orphanRemoval=true
,那么CascadeType.REMOVE
是多余的。null
或另一个Address
对象时。
如果指定了orphanRemoval=true
,则断开的Address
实例会自动删除。这对于清理应该没有从所有者对象(例如Employee
)的引用中存在的依赖对象(例如Address
)非常有用。
如果仅指定了cascade=CascadeType.REMOVE
,则不会自动执行任何操作,因为断开关系不是一项删除操作。
当你从集合中移除一个子实体时,你也会将该子实体从数据库中删除。orphanRemoval还意味着你不能更改父级对象;如果有一个部门拥有员工,一旦你将该员工从一个部门中移除并放入另一个部门,那么在flush/commit(以先到者为准)时,你将无意中将该员工从数据库中删除。教训就是只要确定该父节点的子项不会在其存在期间迁移到其他父节点,就将orphanRemoval设置为true。打开orphanRemoval还会自动将REMOVE添加到级联列表中。
department.remove(emp);
,该员工将从 emp 表中删除,甚至不需要调用 commit()
。 - JavaTechnicalJPA将实体状态转换为SQL语句,例如INSERT、UPDATE或DELETE。
当您使用persist
持久化实体时,您正在安排INSERT语句在EntityManager
被自动或手动刷新时执行。
当您remove
一个实体时,您正在安排DELETE语句,在持久化上下文被刷新时执行。
为了方便,JPA允许您将父实体的状态转换传播到子实体中。
因此,如果您有一个父Post
实体,它与PostComment
子实体具有@OneToMany
关联:
Post
实体中的comments
集合映射如下:
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();
cascade
属性告诉JPA提供程序将实体状态转换从父Post
实体传递到包含在comments
集合中的所有PostComment
实体。
因此,如果删除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
时,JPA提供程序将在从集合中删除子实体时安排一个remove
操作。
所以,在我们的案例中,
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
记录,因为PostComment
实体不再在comments
集合中引用:
DELETE FROM post_comment WHERE id = 1
ON DELETE CASCADE
在外键级别上定义:
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 cascade
和orphanRemoval
选项的优点在于,您还可以从乐观锁定中受益,以防止丢失更新。
如果使用JPA级联机制,则无需使用DDL级别的ON DELETE CASCADE
,否则如果您删除具有多个层级的多个子实体的根实体,则可能会导致非常危险的操作。
CascadeType
影响,它是一种补充机制。现在,您将删除与持久化混淆了。孤儿删除是指删除未引用的关联,而持久化是关于保存新实体。您需要按照答案中提供的链接来更好地理解这些概念。 - Vlad MihalceaDDL中ON DELETE CASCADE
的等效JPA映射是cascade=CascadeType.REMOVE
。孤儿删除指当与其“父”实体的关系被销毁时,依赖实体也会被删除。例如,如果一个子对象从@OneToMany
关系中移除但在实体管理器中没有明确移除它。
cascade=CascadeType.REMOVE
不等同于 ON DELETE CASCADE
。在应用程序代码中执行删除操作不会影响 DDL,而其他操作则在数据库中执行。请参见 https://dev59.com/rWUp5IYBdhLWcg3w_rrT#19696859。 - Grigory Kislin区别在于:
- orphanRemoval = true:当“Child”实体不再被引用时(其父实体可能未被删除),将删除该实体。
- CascadeType.REMOVE:只有在“Parent”被删除时才会删除“Child”实体。
@GaryK的回答非常好,我花了一个小时寻找关于orphanRemoval=true
和CascadeType.REMOVE
之间的区别,他帮助我理解了这个问题。
总结一下:orphanRemoval=true
与CascadeType.REMOVE
相同,只有当我们删除对象(entityManager.delete(object)
)并且希望同时删除子对象时才起作用。
在完全不同的情况下,当我们获取一些数据,例如List<Child> childs = object.getChilds()
,然后删除一个子项(entityManager.remove(childs.get(0))
),使用orphanRemoval=true
将导致与childs.get(0)
对应的实体记录从数据库中被删除。
@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)
学生表:
1 Roy ECE 1
2 Nick ECE 1
1 John $1500
现在,如果我想删除其中一个学生,可以使用
Student student1 = em.find(Student.class,1);
em.remove(student1);
当学生记录被删除时,相应的指导记录也应该被删除,这时候在学生实体中使用CASCADE.REMOVE属性就很有用了。它的作用是删除标识为1的学生以及相应的指导对象(标识为1)。但在本例中,还有另一个学生对象映射到同一指导记录,如果不在指导实体中使用orphanRemoval=true属性,则上述删除代码将无法起作用。