我对 JPA 2.0orphanRemoval
属性有点困惑。
我想当我使用我的 JPA 提供者的数据库生成工具来创建底层数据库 DDL 以具有ON DELETE CASCADE
特定关系时,我可以看到它是必需的。
但是,如果数据库存在并且它已经有一个ON DELETE CASCADE
关系,这是否不足以适当地级联删除?除此之外还有什么作用orphanRemoval
?
干杯
我对 JPA 2.0orphanRemoval
属性有点困惑。
我想当我使用我的 JPA 提供者的数据库生成工具来创建底层数据库 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
实例,则会自动删除。这对于清理在Address
没有来自所有者对象(例如 )的引用的情况下不应该存在的依赖对象(例如 )很有用Employee
。
如果仅cascade=CascadeType.REMOVE
指定,则不会采取自动操作,因为断开关系不是删除操作。
为避免由于孤立删除而导致悬空引用,应仅对包含私有非共享依赖对象的字段启用此功能。
我希望这能让它更清楚。
从集合中删除子实体的那一刻,您也将从数据库中删除该子实体。orphanRemoval 也意味着你不能改变父母;如果有一个部门有员工,一旦您删除该员工并将其放入另一个部门,您将在刷新/提交时无意中将该员工从数据库中删除(以先到者为准)。士气是将 orphanRemoval 设置为 true,只要您确定该父母的孩子在其存在期间不会迁移到其他父母。打开 orphanRemoval 也会自动将 REMOVE 添加到级联列表中。
JPA 将实体状态转换转换为 SQL 语句,如 INSERT、UPDATE 或 DELETE。
当您persist
是一个实体时,您正在安排 INSERT 语句在EntityManager
刷新时自动或手动执行。
当您remove
是一个实体时,您正在调度 DELETE 语句,该语句将在刷新持久性上下文时执行。
为方便起见,JPA 允许您将实体状态转换从父实体传播到子实体。
因此,如果您有一个与子实体关联的父Post
实体:@OneToMany
PostComment
实体中的comments
集合Post
映射如下:
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();
该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
属性设置为 时,JPA 提供者将在从集合中删除子实体时true
安排一个操作。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
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
实体。但是,如果您错误地删除了根实体,这可能是一个非常危险的操作。
JPAcascade
和orphanRemoval
选项的优点是您还可以从乐观锁定中受益,以防止丢失更新。
如果使用 JPA 级联机制,则不需要使用 DDL-level ON DELETE CASCADE
,如果删除在多个级别上具有许多子实体的根实体,这可能是非常危险的操作。
DDL 的等效 JPA 映射ON DELETE CASCADE
是cascade=CascadeType.REMOVE
. 孤立删除意味着当与其“父”实体的关系被破坏时,依赖实体被删除。例如,如果从@OneToMany
关系中删除了一个孩子,而没有在实体管理器中明确删除它。
区别在于:
- orphanRemoval = true:“子”实体在不再被引用时被删除(其父实体可能不会被删除)。
- CascadeType.REMOVE:“子”实体仅在其“父”被删除时才会被删除。
@GaryK 的答案绝对很棒,我花了一个小时寻找解释orphanRemoval = true
vsCascadeType.REMOVE
它帮助我理解。
总结:仅当我们删除对象 ( ) 并且我们希望子对象也被删除时,其orphanRemoval = true
工作方式相同。CascadeType.REMOVE
entityManager.delete(object)
在完全不同的情况下,当我们获取一些数据时,例如List<Child> childs = object.getChilds()
然后删除一个子(entityManager.remove(childs.get(0)
)使用orphanRemoval=true
将导致对应的实体childs.get(0)
从数据库中删除。
在以下场景中,孤儿删除与 ON DELETE CASCADE 具有相同的效果:- 假设我们在学生实体和指南实体之间有一个简单的多对一关系,其中许多学生可以映射到同一个指南,并且在数据库中我们有一个Student 和 Guide 表之间的外键关系,使得学生表具有 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)
学生表:-
1 罗伊欧洲经委会 1
2 尼克 ECE 1
约翰 1 美元 1500 美元
现在,如果我想删除其中一名学生,请使用
Student student1 = em.find(Student.class,1);
em.remove(student1);
当学生记录被删除时,相应的引导记录也应该被删除,这就是学生实体中的 CASCADE.REMOVE 属性出现的地方,它的作用是;它删除了标识符为 1 的学生以及相应的引导对象(标识符1)。但是在这个例子中,还有一个学生对象映射到同一个指南记录,除非我们在指南实体中使用orphanRemoval=true属性,否则上面的删除代码将不起作用。