我们在Hibernate中遇到了一个问题,听起来像是一个bug,它处理oneToManyList(带索引)的orphanRemoval=true。
以下是简化的映射:
public class ParentClass {
[...]
@OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true)
@OnDelete(action = OnDeleteAction.CASCADE)
@Fetch(FetchMode.JOIN)
@OrderColumn(name = "pos", nullable = false)
public List<ChildClass> getChildren() {
return children;
}
}
儿童班:
public class ChildClass {
[...]
@ManyToOne
@JoinColumn(nullable = false, name = "parent_id")
public ParentClass getParent() {
return parent;
}
}
给定此映射,以下场景将失败:
- 从DB获取父级
- 向其中添加子项
- 从DB(不是同一个实体)获取其他内容,生成部分刷新
- 从父对象中删除子对象
- 退出交易
这是代码
@Transactional
public void test() {
// 1)
ParentClass parent = entityManager.find(ParentClass.class, "some-id");
// 2)
ChildClass child = new ChildClass(parent);
parent.getChildren().add(child);
// 3)
entityManager.find(SomethingElse.class, "2");
// 4)
parent.getChildren().remove(child);
}
在这种情况下,子分配被插入到DB中,而不是在事务结束时删除。
然而,如果我们不执行步骤3),则子分配不会正确地持久化在DB中
那是个虫子吗?一个错误的映射?有解决办法吗?
是的,这是一个错误。我敢打赌,你没有使用Hibernate的上一个版本(4.3.8),它与这个问题有关(如果不是同一个问题的话):[HHHH-9330]orphanRemoval=true在双向关系中不起作用(没有级联)即使你使用上一个版本,这个bug也可能仍然存在。如果是,请报告它,然后在这里的某个地方报告错误的URL。解决方法:只有在确定必须持久化时才尝试添加子项,或者也尝试将子项中的父项设置为空:
child.setParent(null);
作为另一种解决方法,您可以尝试使用Hibernate会话,而不是EntityManager(正如Hibernate论坛中所写的那样)。
删除Child:时需要设置关联的两侧
child.setParent(null);
parent.getChildren().remove(child);
在您的情况下,孤立删除是不够的。如果一对多是单向关联,那么您可以简单地从列表中删除Child,并且删除将传播到Child实体。
当你有双向关联时,你需要让双方同步。这就是为什么在删除时需要将子实体与父实体取消关联。
有一个JPA注释@PreRemove
,在您的特定情况下可能会有所帮助。尝试将其添加到子实体,以便它可以从父实体中删除:
@PreRemove
protected void beforeRemove(){
parent.getChildren().remove(this);
}