在 JPA 中批量删除行同时将删除级联到子记录的最佳方法是什么

What is the best way to bulk delete rows in JPA while also cascading the delete to child records

我正在尝试在我的实体中进行批量删除,最好的解决方案是使用 CriteriaDelete。但是 CriteriaDelete 不会级联(至少对我来说不是)。

所以,我唯一的解决办法似乎是先 select 然后分别删除每个元素。这对我来说似乎没有错。

有没有人更了解如何进行批量删除?它实际上是更好的方法吗?

如果有帮助,我正在使用 EclipseLink 2.5.2。

JPQL BULK DELETE(无论是使用基于字符串的 JPQL 还是使用 Criteria JPQL)不打算级联(即遵循字段的级联类型设置)。如果您想要级联,那么您可以将数据存储设置为使用真实的 FOREIGN KEYs,或者您拉回要删除的对象并调用 EntityManager.remove().

如果您真的很关心执行此批量删除所需的时间,我建议您使用 JPQL 来删除您的实体。当您发出 DELETE JPQL 查询时,它会直接对这些实体发出删除操作,而不会首先检索它们。

int deletedCount = entityManager.createQuery("DELETE FROM Country").executeUpdate(); 

您甚至可以使用查询 API 根据这些实体的某些参数进行条件删除,如下所示

Query query = entityManager.createQuery("DELETE FROM Country c 
                              WHERE c.population < :p");
int deletedCount = query.setParameter(p, 100000).executeUpdate();

executeUpdate 将 return 操作完成后删除的行数。

如果您在实体中设置了适当的级联类型,例如 CascadeType.ALL(或)CascadeType.REMOVE,那么上面的查询将为您解决问题。

@Entity
class Employee {

    @OneToOne(cascade=CascadeType.REMOVE)
    private Address address;

}

有关详细信息,请查看 this and this

选项是:

  1. 在映射、加载实体上使用 cascade.Remove 设置 并在每个
  2. 上调用 em.remove
  3. 在主要实体上使用批量删除并设置“删除时 CASCADE”数据库选项设置,以便数据库将级联 给你删除。 EclipseLink 有一个 @CascadeOnDelete 注释 让它知道 "ON DELETE CASCADE" 是在关系上设置的,或者 如果使用 JPA 生成 DDL,则创建它:http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
  4. 在删除主要实体之前,使用多次批量删除删除可能被引用的子实体。例如:"Delete FROM Child c where c.parent = (select p from Parent P where [delete-conditions])" 和 "Delete FROM Parent p where [delete-conditions]" 有关详细信息,请参阅 http://docs.oracle.com/middleware/1212/toplink/OTLCG/queries.htm#OTLCG94370 的第 10.2.4 节。

JPA CriteriaDelete 如何工作

JPA CriteriaDelete 语句生成 JPQL 批量删除语句,该语句被解析为 SQL 批量删除语句。

因此,以下 JPA CriteriaDelete 语句:

CriteriaBuilder builder = entityManager.getCriteriaBuilder();
    
CriteriaDelete<PostComment> delete = builder.createCriteriaDelete(PostComment.class);

Root<T> root = delete.from(PostComment.class);

int daysValidityThreshold = 3;

delete.where(
    builder.and(
        builder.equal(
            root.get("status"), 
            PostStatus.SPAM
        ),
        builder.lessThanOrEqualTo(
            root.get("updatedOn"), 
            Timestamp.valueOf(
                LocalDateTime
                .now()
                .minusDays(daysValidityThreshold)
            )
        )
    )
);

int deleteCount = entityManager.createQuery(delete).executeUpdate();

生成此 SQL 删除查询:

DELETE FROM
    post_comment
WHERE
    status = 2 AND
    updated_on <= '2020-08-06 10:50:43.115'

因此,没有 entity-level 级联,因为删除是使用 SQL 语句完成的,而不是通过 EntityManager.

批量删除级联

要在执行批量删除时启用级联,您需要在声明 FK 约束时使用 DDL-level 级联。

ALTER TABLE post_comment 
ADD CONSTRAINT FK_POST_COMMENT_POST_ID
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE

现在,当执行以下批量删除语句时:

DELETE FROM
    post
WHERE
    status = 2 AND
    updated_on <= '2020-08-02 10:50:43.109'

数据库将删除引用 post 行已删除的 post_comment 记录。

执行 DDL 的最佳方式是通过自动架构迁移工具,例如 Flyway,因此外键定义应驻留在迁移脚本中。

如果您使用HBM2DLL工具生成迁移脚本,那么,在PostComment class中,您可以使用以下映射来生成上述DDL语句:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(foreignKey = @ForeignKey(name = "FK_POST_COMMENT_POST_ID"))
@OnDelete(action = OnDeleteAction.CASCADE)
private Post post;