JPA:DELETE WHERE不会删除子项并抛出exception

由于JPQL查询,我试图从MOTHER删除大量行。

Mother类定义如下:

 @Entity @Table(name = "MOTHER") public class Mother implements Serializable { @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother", orphanRemoval = true) private List children; } @Entity @Table(name = "CHILD") public class Child implements Serializable { @ManyToOne @JoinColumn(name = "MOTHER_ID") private Mother mother; } 

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

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

抛出exception:

 ERROR - ORA-02292: integrity constraint  violated - child record found 

当然,我可以首先选择我要删除的所有对象,然后在迭代它之前将它们检索到列表中以删除所有检索到的对象,但是这样的解决方案的性能会非常糟糕!

那么有没有办法利用以前的映射来有效地删除所有Mother对象和所有与它们相关联的Child对象,而不首先为所有子节点编写查询?

DELETE(和INSERT)不会通过JPQL查询中的关系级联。 这清楚地说明了规范:

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

幸运地坚持并通过实体管理器删除(当定义了级联属性时)。

你可以做什么:

  • 获取应删除的所有Mother实体实例。
  • 对于他们每个人调用EntityManager.remove()。

代码是这样的:

 String selectQuery = "SELECT m FROM Mother m WHERE some_condition"; List mothersToRemove = entityManager.createQuery(selectQuery).getResultList(); for (Mother m: mothersToRemove) { em.remove(m); } 

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

当您使用HQL delete语句发出查询时,您可能会绕过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); 

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

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

JPA CascadeType.ALL不会删除孤儿

编辑

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

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

我必须说我不确定查询中的’删除’是否会删除所有相关的onetomany实体,因为’MikKo Maunu’说。 我会说会的。 问题是(抱歉没有尝试这一点),JPA / Hibernate将要做的只是执行’真正的sql删除’,而那时母亲和子实例没有被管理,它无法知道哪个孩子要移除的实例。 orphanRemoval是一个很好的帮助,但在这种情况下不是。 我会

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

2)如果1)不起作用,不要做所有母亲/孩子取得为JPA图层清楚地做一切,并且在你使用的那个之前运行查询(在同一个交易中,但我不确定你是否需要在他们之间运行’em.flush’)

 DELETE FROM Child c WHERE c.mother  

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

更新:

Mikko Maunu是对的,JPQL中的批量删除不会级联。 根据我的建议使用两个查询很好。

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

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

在例如OpenJPA中,您只需要将您的EM转换为OpenJPAEntityManager,最好通过OpenJPAPersistence.cast(em).removeAll(…)

您可以在RDBMS上继续使用外键约束删除那些Mother 。 假设您从实体生成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; }