由于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>孩子们?
答案 0 :(得分:31)
DELETE(和INSERT)不会通过JPQL查询中的关系级联。这清楚地说明了规范:
删除操作仅适用于指定类的实体和 它的子类。它不会级联到相关实体。
幸运地坚持并通过实体管理器删除(当定义了级联属性时)。
你能做什么:
代码是这样的:
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(...)