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上找到一个可用于检查上述行为和解决方案的简单示例应用程序。
翻译自: https://www.javacodegeeks.com/2013/11/efficiently-delete-data-with-jpa-and-hibernate.html
jpa和hibernate