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 上中继以使用外键约束删除那些 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 吗?

以上是关于JPA:DELETE WHERE 不删除子级并抛出异常的主要内容,如果未能解决你的问题,请参考以下文章

Spring data JPA:如何启用级联删除而不引用父级中的子级?

在 DELETE CASCADE 上,深度超过 15 级的自引用 MySQL 表失败

如何使用 where 子句从 Firestore 中删除文档

Spring数据JPA findFirst用where子句(过滤)和order by,抛出内部异常

删除 Kafka StateStore 中的记录不起作用(在 .delete(key) 上抛出 NullPointerException)

我的查询根本不起作用并抛出错误