Вы можете столкнуться с ситуацией, когда вам нужно выполнить массовое удаление огромного количества наборов данных, хранящихся в реляционной базе данных. Если вы используете JPA с Hibernate в качестве базового преобразователя OR, вы можете попытаться вызвать метод remove () EntityManager следующим образом:
1
2
3
4
|
public void removeById( long id) { RootEntity rootEntity = entityManager.getReference(RootEntity. class , id); entityManager.remove(rootEntity); } |
Прежде всего, мы загружаем эталонное представление сущности, которую мы хотим удалить, и затем передаем эту ссылку в EntityManager. Давайте предположим, что RootEntity сверху имеет дочернее отношение к классу ChildEntity:
1
2
|
@OneToMany (mappedBy = "rootEntity" , fetch = FetchType.EAGER, cascade = CascadeType.ALL) private Set childEntities = new HashSet( 0 ); |
Если теперь мы включим свойство show_sql в hibernate, мы будем удивляться, какие выражения SQL выдаются:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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? Ответ заключается в использовании простого запроса DELETE, подобного следующему. Но из-за ограничения целостности дочерней таблицы сначала необходимо удалить все зависимые дочерние объекты. Следующий код демонстрирует, как:
1
2
3
4
5
|
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(); |
Приведенный выше код приводит к трем операторам SQL, которые мы ожидали бы, вызвав remove (). Теперь вы можете утверждать, что этот способ удаления сложнее, чем просто вызов метода EntityManager remove (). Он также игнорирует аннотации, такие как @OneToMany и @ManyToOne, которые мы поместили в два класса сущностей. Так почему бы не написать некоторый код, который использует знания о двух сущностях, которые уже существуют в двух файлах классов? Прежде всего, мы ищем аннотации @OneToMany, используя отражение в классе RootEntity, извлекаем тип дочернего объекта, а затем ищем его поле обратной связи, аннотированное @ManyToOne. Сделав это, мы можем легко написать три оператора SQL более общим способом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
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 .