【问题标题】:JPA EntityManager.merge() attemps to cascade the update to deleted entitiesJPA EntityManager.merge() 尝试将更新级联到已删除的实体
【发布时间】: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;
}

出现问题的情况如下:

  1. 用户创建一个新的 Parent 实例,并通过调用 createChildren() 方法基于他们选择的模板创建新的 Child 实例。模板定义了创建的子节点的数量和属性。
  2. 用户保存父级,将持久性级联到子级。
  3. 用户注意到使用的模板有误。他更改了模板并保存,这会导致删除旧的孩子并创建新的孩子。

老子实体的删除通常会由 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 以使其清晰。

【问题讨论】:

    标签: java hibernate jpa


    【解决方案1】:

    尝试在删除之前从父级中删除子级,这样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);
    }
    

    【讨论】:

    • 我在 createChildren() 方法中从父级清除子级。我的代码 sn-p 可能对此有点不清楚,我对其进行了更新以使其明确。
    • 我尝试了您建议的确切顺序,但没有任何效果。我仍然得到 EntityNotFoundException。
    • 几个月后我又回到了这个问题,并通过1.从父母那里清除老孩子并为他们调用entityManager.remove(...)来解决它, 2. 手动刷新 EntityManager,然后 3. 才创建新的子项。所以它是我们的代码 sn-ps 的混合;我本来冲的太早了,你根本没做。如果没有手动刷新,所有数据库操作都会排队,并且删除最终会在最后运行,从而导致违反约束。但是感谢您为我指明了正确的方向。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-22
    • 1970-01-01
    • 2013-03-23
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多