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

时间:2011-10-19 17:33:09

标签: java hibernate jpa jpql

由于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个对象,而无需先写入所有<的查询/ strong>孩子们?

5 个答案:

答案 0 :(得分:31)

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

  

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

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

你能做什么:

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

代码是这样的:

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

答案 1 :(得分:1)

您是否尝试过使用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);

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

答案 2 :(得分:1)

您可以在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;    
}

答案 3 :(得分:0)

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

JPA CascadeType.ALL does not delete orphans

修改

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

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

答案 4 :(得分:0)

我必须说我不确定查询中的'删除'是否会删除所有相关的onetomany实体,因为'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,最好通过OpenJPAPersistence.cast(em).removeAll(...)