【问题标题】:JPA: DELETE WHERE does not delete children and throws an exceptionJPA:DELETE WHERE 不删除子级并抛出异常
【发布时间】:2011-12-11 03:32:56
【问题描述】:

感谢 JPQL 查询,我正在尝试从 MOTHER 中删除大量行。

Mother 类定义如下:

@Entity
@Table(name = "MOTHER")
public class Mother implements Serializable {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother", 
               orphanRemoval = true)
    private List<Child> children;    
}

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID")
    private Mother mother;    
}

如您所见,Mother 类有“孩子”并且在执行以下查询时:

String deleteQuery = "DELETE FROM MOTHER WHERE some_condition";
entityManager.createQuery(deleteQuery).executeUpdate();

抛出异常:

ERROR - ORA-02292: integrity constraint <constraint name> violated - 
                   child record found

当然,我可以先选择我要删除的所有对象并将它们检索到一个列表中,然后再遍历它以删除所有检索到的对象,但是这样的解决方案的性能会很糟糕!

那么有没有办法利用之前的映射来有效地删除所有Mother 对象和所有与之关联的Child 对象,而无需先编写all 的查询孩子们?

【问题讨论】:

    标签: java hibernate jpa jpql


    【解决方案1】:

    您是否尝试过使用session.delete() 或等效的EntityManager.remove()

    当您使用 HQL 删除语句发出查询时,您可能会绕过 Hibernate 的级联机制。看看这个 JIRA 问题:HHH-368

    您可以通过以下方式实现:

    Mother mother = session.load(Mother.class, id);
    // If it is a lazy association, 
    //it might be necessary to load it in order to cascade properly
    mother.getChildren(); 
    session.delete(mother);
    

    我现在不确定是否需要初始化集合以使其正确级联。

    【讨论】:

      【解决方案2】:

      DELETE(和 INSERT)不会通过 JPQL 查询中的关系进行级联。这在specification中拼写得很清楚:

      删除操作仅适用于指定类的实体,并且 它的子类。它不会级联到相关实体。

      幸运的是,通过实体管理器进行持久化和删除(当定义了级联属性时)。

      你可以做什么:

      • 获取所有应删除的 Mother 实体实例。
      • 为它们中的每一个调用 EntityManager.remove()。

      代码是这样的:

      String selectQuery = "SELECT m FROM Mother m WHERE some_condition";  
      List<Mother> mothersToRemove = entityManager
          .createQuery(selectQuery)
          .getResultStream()
          .forEach(em::remove);
      

      【讨论】:

      • 遗憾的是,这破坏了批量删除带来的性能优势,并且要在除了玩具之外的任何情况下实施此解决方案,您需要将 selectQuery 包装到分页基础架构中,该基础架构会清除每页的 entermanager 以避免用完拥有大量母亲的记忆。
      • 每次看到循环中的数据库调用时,我都会感到畏缩。这太慢了,资源密集型,效率低下!总有更好的方法! :)
      • 实际上,通常 EntityManager.remove() 还没有调用数据库,这会在刷新/提交期间发生。
      • 确实如此。 JPA implementations will operate in-memory until a flush is required。这是使用 ORM 的潜在好处之一。
      【解决方案3】:

      这是相关的,如果您使用 Hibernate,可能会提供解决方案。

      JPA CascadeType.ALL does not delete orphans

      编辑

      由于甲骨文是给你错误的人,你可以利用甲骨文级联删除来解决这个问题。但是,这可能会产生不可预知的结果:由于 JPA 没有意识到您正在删除其他记录,因此即使它们已被删除,这些对象也可能保留在缓存中并被使用。这仅适用于您使用的 JPA 实现具有缓存并配置为使用它的情况。

      以下是有关在 Oracle 中使用级联删除的信息:http://www.techonthenet.com/oracle/foreign_keys/foreign_delete.php

      【讨论】:

      • Hibernate 的 deleteOrphans 注释对此没有帮助。试了一下。
      【解决方案4】:

      我必须说,我不确定查询中的“删除”是否会像“MikKo Maunu”所说的那样删除您案例中所有相关的单机实体。我会说会的。 问题是(很抱歉没有尝试这个)JPA/Hibernate 将做的只是执行“真正的 sql 删除”,而那些母子实例当时没有被管理,它无法知道哪个子要删除的实例。 orphanRemoval 有很大帮助,但在这种情况下不是。 我会

      1) 尝试将 'fetch = FetchType.EAGER' 添加到 onetomany 关系中(这也可能是性能问题)

      2) 如果 1) 不起作用,则不要进行所有母/子获取以使 JPA 层的一切都清楚,只需在您使用的查询之前运行查询(在同一事务中,但我不确定是否你不需要在它们之间运行'em.flush')

      DELETE FROM Child c WHERE c.mother <the condition>
      

      (删除通常是 JPA/Hibernate 的麻烦,我用一个例子来谴责 ORM 的使用,它本质上是应用程序中的一个附加层,以使事情“更容易”。唯一的好处是,ORM问题/错误通常是在开发阶段发现的。我的钱总是在 MyBatis 上,我认为这更干净。)

      更新:

      Mikko Maunu 是对的,JPQL 中的批量删除不会级联。不过按照我的建议使用两个查询就可以了。

      棘手的是,持久化上下文(由 EntityManager 管理的所有实体)与批量删除的操作不同步,因此它(在我建议的情况下是两个查询)应该在单独的事务中运行。

      更新 2: 如果使用手动删除而不是批量删除,许多 JPA 提供程序和 Hibernate 也会在其 EntityManager 实现中提供 removeAll(...) 方法或类似的(非 API)方法。它使用起来更简单,并且在性能方面可能更有效。

      在例如OpenJPA,您只需要将您的 EM 转换为 OpenJPAEntityManager,最好使用 OpenJAPersistence.cast(em).removeAll(...)

      【讨论】:

      • 你不必尝试,JPQL中的DELETE不会级联。规范中明确拼写:“删除操作仅适用于指定类及其子类的实体。它不会级联到相关实体。”
      【解决方案5】:

      您可以在 RDBMS 上中继以使用外键约束删除那些 Mothers。 这假设您从实体生成 DDL:

      @Entity
      @Table(name = "CHILD")
      public class Child  implements Serializable {
      
          @ManyToOne
          @JoinColumn(name = "MOTHER_ID", foreignKey = @ForeignKey(foreignKeyDefinition =
              "FOREIGN KEY(MOTHER_ID) REFERENCES MOTHER(ID) ON DELETE CASCADE",
              value = ConstraintMode.CONSTRAINT))
          private Mother mother;    
      }
      

      【讨论】:

      • 我刚刚遇到了这个答案,想知道如果 entityManager/Cache 持有对子实体的引用会发生什么?我们必须设置 orphanRemoval 吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-28
      相关资源
      最近更新 更多