JPA EntityManager.merge()尝试将更新级联到已删除的实体

时间:2015-03-11 14:40:21

标签: java hibernate jpa

我遇到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;
}

出现问题的情况如下:

  1. 用户创建一个新的Parent实例,并通过调用createChildren()方法基于他们选择的模板创建新的Child实例。该模板定义了已创建子项的数量和属性。
  2. 用户保存父级,将父级保存到子级。
  3. 用户注意到使用的模板错误。他更改了模板并保存,导致删除旧子项并创建新子项。
  4. 通常,删除旧子项将由orphanRemoval属性自动处理,但Child实体具有多列唯一索引,并且基于新模板创建的一些新子项可以在所有列中具有相同的值作为一些原始孩子的索引。将更改刷新到数据库时,JPA会在删除(or at least Hibernate does)之前执行插入和更新,并发生约束冲突。 Oracle的延迟约束可以解决这个问题,但我们也支持MS SQL,AFAIK不支持延迟约束(如果我错了,请纠正我)。

    因此,为了解决这个问题,我手动删除旧子项,刷新更改,创建新子项并保存更改。下面的人工代码片段显示了现在正在发生的事情的基本部分。由于我们的框架的工作方式,传递给此方法的实体始终处于分离状态(我担心这是问题的一部分)。

    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。

    更新

    我们实际上也是从父母那里清除孩子,这可能有点含糊不清,所以我更新了代码片段以便说清楚。

1 个答案:

答案 0 :(得分:2)

尝试删除子级中的子项,然后再删除它们,MERGE不能级联它们,因为它们不在父级的集合中。

for (Child child : parent.getChildren()) {
    // Have to run a find since the entities are detached
    Child c = entityManager.find(Child.class, child.getId());
    parent.getChildren().remove(c); // ensure that the child is actually removed
    entityManager.remove(c);
}

<强>更新

我仍然认为操作的顺序是这里问题的原因,如果这样做可以尝试

public void createNewChildren(Parent parent, Template template) {
  for (Child child : parent.getChildren()) {
    // Have to run a find since the entities are detached
    Child c = entityManager.find(Child.class, child.getId());
    parent.getChildren().remove(c); // ensure that the child is actually removed
    c.setParent(null);
    entityManager.remove(c);
  }
  parent.createChildren(template);
  entityManager.merge(parent);
}