【问题标题】:What is the best way to bulk delete rows in JPA while also cascading the delete to child records在 JPA 中批量删除行同时将删除级联到子记录的最佳方法是什么
【发布时间】:2015-05-20 04:58:34
【问题描述】:

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

所以,我唯一的解决方案似乎是先选择并分别删除每个元素。这对我来说似乎没有错。

有人对如何进行批量删除有更好的了解吗?这真的是更好的方法吗?

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

【问题讨论】:

  • 您可以在数据库中使用ON DELETE 约束吗?

标签: java jpa eclipselink jpa-2.1 bulk-delete


【解决方案1】:

选项有:

  1. 在映射上使用 cascade.Remove 设置,加载实体 并在每个上调用 em.remove
  2. 在主实体上使用批量删除并具有“ON DELETE CASCADE" 数据库选项设置,以便数据库将级联 为你删除。 EclipseLink 有一个 @CascadeOnDelete 注释 让它知道在关系上设置了“ON DELETE CASCADE”,或者 如果使用 JPA 生成 DDL,则创建它:http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
  3. 在删除主实体之前,使用多个批量删除删除可能被引用的子实体。例如:“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 节详情。

【讨论】:

    【解决方案2】:

    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'
    

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

    批量删除级联

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

    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 类中,您可以使用以下映射来生成上述 DDL 语句:

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

    【讨论】:

      【解决方案3】:

      如果您真的关心执行此批量删除所需的时间,我建议您使用 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 将在操作完成后返回删除的行数。

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

      @Entity
      class Employee {
      
          @OneToOne(cascade=CascadeType.REMOVE)
          private Address address;
      
      }
      

      更多详情,请查看thisthis

      【讨论】:

      【解决方案4】:

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

      【讨论】:

      • “使用真正的外键”是什么意思?你能详细说明一下吗?
      • 在您的 RDBMS 表定义中添加 FOREIGN KEY 约束,这些约束可以是“ON DELETE CASCADE”
      • 当您需要删除这么多实体时,EntityManager.remove() 不是一个好主意,因为您不必要地加载这些实体只是为了能够在每个实体上调用 remove。使用 JPQL,您可以轻松避免那些 select 查询并减少到数据库服务器的往返:)
      • @NeilStockton 我已经为每个实体定义了 FK,它们存在于我的架构中,但即使我在关系上设置了``CascadeType.REMOVE`,在架构上我也有 ON DELETE NO ACTION
      • 看看我的回答,您可以在实体上设置级联类型,而无需在数据库表之间建立明确的关系。如果不是,您必须在父表上编写删除触发器以删除所有子表,或者您必须采用先删除子表然后手动从代码中手动删除父表的旧方法,这非常令人厌烦:-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多