JPA:DELETE WHERE 不删除子级并抛出异常
Posted
技术标签:
【中文标题】JPA:DELETE WHERE 不删除子级并抛出异常【英文标题】:JPA: DELETE WHERE does not delete children and throws an exception 【发布时间】: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 的查询孩子们?
【问题讨论】:
【参考方案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 上中继以使用外键约束删除那些 Mother
s。
这假设您从实体生成 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 吗?以上是关于JPA:DELETE WHERE 不删除子级并抛出异常的主要内容,如果未能解决你的问题,请参考以下文章
Spring data JPA:如何启用级联删除而不引用父级中的子级?
在 DELETE CASCADE 上,深度超过 15 级的自引用 MySQL 表失败
如何使用 where 子句从 Firestore 中删除文档
Spring数据JPA findFirst用where子句(过滤)和order by,抛出内部异常
删除 Kafka StateStore 中的记录不起作用(在 .delete(key) 上抛出 NullPointerException)