【发布时间】:2015-03-12 17:45:44
【问题描述】:
我遇到了 EntityManager.merge() 的问题,其中合并级联到已从数据库中删除的其他实体。假设我有以下实体:
@Entity
public class Parent {
@OneToMany(cascade = CascadeType.ALL, orphanremoval = true, mappedBy = "parent")
private List<Child> children;
public void clearChildren() { children.clear(); }
public void createChildren(Template template) { ... }
}
@Entity
public class Child {
@ManyToOne
@JoinColumn(name = "parentId")
private Parent parent;
}
出现问题的情况如下:
- 用户创建一个新的 Parent 实例,并通过调用 createChildren() 方法基于他们选择的模板创建新的 Child 实例。模板定义了创建的子节点的数量和属性。
- 用户保存父级,将持久性级联到子级。
- 用户注意到使用的模板有误。他更改了模板并保存,这会导致删除旧的孩子并创建新的孩子。
老子实体的删除通常会由 orphanRemoval 属性自动处理,但子实体具有多列唯一索引,并且基于新模板创建的一些新子实体可以在所有列中具有相同的值索引的一些原始子项。当更改刷新到数据库时,JPA 在删除之前执行插入和更新 (or at least Hibernate does),并发生约束冲突。 Oracle 的延迟约束可以解决这个问题,但我们也支持 MS SQL,AFAIK 不支持延迟约束(如果我错了,请纠正我)。
所以为了解决这个问题,我手动删除了旧的孩子,刷新更改,创建新的孩子,并保存我的更改。下面的人工代码 sn-p 显示了现在正在发生的事情的基本部分。由于我们框架的工作方式,传递给此方法的实体始终处于分离状态(恐怕这是问题的一部分)。
public void createNewChildren(Parent parent, Template template) {
for (Child child : parent.getChildren()) {
// Have to run a find since the entities are detached
entityManager.remove(entityManager.find(Child.class, child.getId()));
}
entityManager.flush();
parent.clearChildren();
parent.createChildren(template);
entityManager.merge(parent); // EntityNotFoundException is thrown
}
最后一行抛出异常,因为 EntityManager 尝试加载旧的子级并合并它们,但由于它们已被删除而失败。问题是,为什么它首先会尝试加载它们?更重要的是,我怎样才能防止它?我想到的唯一可以因为这是一个过时的缓存问题。我无法刷新父级,因为它可以包含其他未保存的更改并且这些更改会丢失(加上它已分离)。在删除它们之前,我尝试将每个子对象的父引用显式设置为 null,并在删除它们后尝试将旧子对象从二级缓存中逐出。都没有帮助。我们没有以任何方式修改 JPA 缓存设置。
我们使用的是 Hibernate 4.3.5。
更新:
我们实际上也在从父级中清除子级,这可能最初有点模棱两可,所以我更新了代码 sn-ps 以使其清晰。
【问题讨论】: