我对 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实体:@OneToManyPostComment
实体中的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 CASCADEFK 级别定义:
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 = truevsCascadeType.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属性,否则上面的删除代码将不起作用。