使用JPA和Hibernate有效删除数据

您可能会遇到必须对关系数据库中存储的大量数据集执行批量删除的情况。 如果您将JPA与Hibernate一起用作基础OR映射器,则可以尝试通过以下方式调用EntityManager的remove()方法:

public void removeById(long id) {
    RootEntity rootEntity = entityManager.getReference(RootEntity.class, id);
    entityManager.remove(rootEntity);
}

首先,我们加载要删除的实体的引用表示形式,然后将此引用传递给EntityManager。 假设上面的RootEntity与名为ChildEntity的类有子关系:

@OneToMany(mappedBy = "rootEntity", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set childEntities = new HashSet(0);

如果现在打开hibernate的属性show_sql,我们将想知道发出什么SQL语句:

select
        rootentity0_.id as id5_1_,
        rootentity0_.field1 as field2_5_1_,
        rootentity0_.field2 as field3_5_1_,
        childentit1_.PARENT as PARENT5_3_,
        childentit1_.id as id3_,
        childentit1_.id as id4_0_,
        childentit1_.field1 as field2_4_0_,
        childentit1_.field2 as field3_4_0_,
        childentit1_.PARENT as PARENT4_0_
    from
        ROOT_ENTITY rootentity0_
    left outer join
        CHILD_ENTITY childentit1_
            on rootentity0_.id=childentit1_.PARENT
    where
        rootentity0_.id=?

    delete
    from
        CHILD_ENTITY
    where
        id=?

   delete
   from
       ROOT_ENTITY
   where
       id=?

为什么Hibernate首先将所有数据加载到内存中以便随后立即删除该数据? 原因是JPA的生命周期要求该对象处于“托管”状态,然后才能将其删除。 仅在这种状态下,所有生命周期功能(如拦截器)才可用(请参阅此处 )。 因此,Hibernate会在删除之前发出SELECT查询,以便将RootEntity和ChildEntity都转移到“托管”状态。 但是,如果我们只想删除RootEntity和ChildEntity,并且知道RootEntity的ID,该怎么办? 答案是使用简单的DELETE查询,如下面的查询。 但是由于子表的完整性约束,我们首先必须删除所有依赖的子实体。 以下代码演示了如何:

List childIds = entityManager.createQuery("select c.id from ChildEntity c where c.rootEntity.id = :pid").setParameter("pid", id).getResultList();
for(Long childId : childIds) {
    entityManager.createQuery("delete from ChildEntity c where c.id = :id").setParameter("id", childId).executeUpdate();
}
entityManager.createQuery("delete from RootEntity r where r.id = :id").setParameter("id", id).executeUpdate();

上面的代码通过调用remove()产生了我们期望的三个SQL语句。 现在您可能会说,这种删除方式比仅调用EntityManager的remove()方法更为复杂。 它还会忽略我们已放置在两个实体类中的注解,例如@OneToMany和@ManyToOne。 那么,为什么不编写一些代码来使用关于两个类文件中已经存在的两个实体的知识呢? 首先,我们在RootEntity类中使用反射查找@OneToMany批注,提取子实体的类型,然后查找其后向字段,并用@ManyToOne批注。 完成此操作后,我们可以轻松地以更通用的方式编写三个SQL语句:

public void delete(EntityManager entityManager, Class parentClass, Object parentId) {
    Field idField = getIdField(parentClass);
    if (idField != null) {
        List oneToManyFields = getOneToManyFields(parentClass);
        for (Field field : oneToManyFields) {
            Class childClass = getFirstActualTypeArgument(field);
            if (childClass != null) {
                Field manyToOneField = getManyToOneField(childClass, parentClass);
                Field childClassIdField = getIdField(childClass);
                if (manyToOneField != null && childClassIdField != null) {
                    List childIds = entityManager.createQuery(String.format("select c.%s from %s c where c.%s.%s = :pid", childClassIdField.getName(), childClass.getSimpleName(), manyToOneField.getName(), idField.getName())).setParameter("pid", parentId).getResultList();
                    for (Long childId : childIds) {
                        entityManager.createQuery(String.format("delete from %s c where c.%s = :id", childClass.getSimpleName(), childClassIdField.getName())).setParameter("id", childId).executeUpdate();
                    }
                }
            }
        }
        entityManager.createQuery(String.format("delete from %s e where e.%s = :id", parentClass.getSimpleName(), idField.getName())).setParameter("id", parentId).executeUpdate();
    }
}

上面的代码中的方法getFirstActualTypeArgument(),getManyToOneField(),getIdField()和getOneToManyFields()并未在此处显示,但是听起来像它们的名字。 实施后,我们可以轻松删除所有以树的根开头的实体。

  • 可以在github上找到一个可用于检查上述行为和解决方案的简单示例应用程序。

参考:Martin's Developer World博客上, 使用JPA和Hibernate从我们的JCG合作伙伴 Martin Mois 有效地删除了数据

翻译自: https://www.javacodegeeks.com/2013/11/efficiently-delete-data-with-jpa-and-hibernate.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值