Начиная с версии 2.0 JPA EntityManager
предлагает метод getCriteriaBuilder()
для динамического построения запросов на выборку без необходимости конкатенации строк с использованием языка запросов постоянства Java (JPQL). В версии 2.1 этот CriteriaBuilder
предлагает два новых метода createCriteriaDelete()
и createCriteriaUpdate()
которые позволяют формулировать запросы на удаление и обновление с использованием API критериев.
В целях иллюстрации давайте используем простой пример использования наследования с двумя сущностями Person
и Geek
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
@Entity @Table (name = "T_PERSON" ) @Inheritance (strategy = InheritanceType.JOINED) public class Person { @Id @GeneratedValue private Long id; @Column (name = "FIRST_NAME" ) private String firstName; @Column (name = "LAST_NAME" ) private String lastName; ... } @Entity @Table (name = "T_GEEK" ) @Access (AccessType.PROPERTY) public class Geek extends Person { private String favouriteProgrammingLanguage; ... } |
Чтобы удалить всех вундеркиндов из нашей базы данных, которые предпочитают Java в качестве языка программирования, мы можем использовать следующий код, используя новый createCriteriaDelete()
EntityManager:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
EntityTransaction transaction = null ; try { transaction = entityManager.getTransaction(); transaction.begin(); CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaDelete<Geek> delete = builder.createCriteriaDelete(Geek. class ); Root<Geek> geekRoot = delete.from(Geek. class ); delete.where(builder.equal(geekRoot.get( "favouriteProgrammingLanguage" ), "Java" )); int numberOfRowsUpdated = entityManager.createQuery(delete).executeUpdate(); LOGGER.info( "Deleted " + numberOfRowsUpdated + " rows." ); transaction.commit(); } catch (Exception e) { if (transaction != null && transaction.isActive()) { transaction.rollback(); } } |
Как и в чистом SQL, мы можем использовать метод from()
чтобы указать таблицу, к которой должен быть выполнен запрос на удаление, и where()
для объявления наших предикатов. Таким образом, API критериев позволяет определять операции массового удаления динамическим способом, не используя слишком много конкатенаций строк.
Но как выглядит SQL, который создан? Прежде всего, поставщик ORM должен обратить внимание на то, что мы JOINED
из иерархии наследования с помощью стратегии JOINED
, что означает, что у нас есть две таблицы T_PERSON
и T_GEEK
где во второй таблице хранится ссылка на родительскую таблицу. Hibernate в версии 4.3.8.Final
создает следующие операторы 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
30
31
32
33
34
35
36
37
38
39
40
41
42
|
insert into HT_T_GEEK select geek0_.id as id from T_GEEK geek0_ inner join T_PERSON geek0_1_ on geek0_.id=geek0_1_.id where geek0_.FAV_PROG_LANG=?; delete from T_GEEK where ( id ) IN ( select id from HT_T_GEEK ); delete from T_PERSON where ( id ) IN ( select id from HT_T_GEEK ) delete from HT_T_GEEK; |
Как мы видим, Hibernate заполняет временную таблицу идентификаторами гиков / людей, которые соответствуют нашим критериям поиска. Затем он удаляет все строки из таблицы geek, а затем все строки из таблицы person. Наконец временная таблица очищается.
Последовательность операторов удаления ясна, поскольку таблица T_GEEK
имеет ограничение внешнего ключа для столбца id таблицы T_PERSON
. Следовательно, строки в дочерней таблице должны быть удалены перед строками в родительской таблице. Причина, по которой Hibernate создает временную таблицу, объясняется в этой статье . Подводя итог, можно сказать, что основная проблема заключается в том, что запрос ограничивает строки, которые будут удалены в столбце, который существует только в дочерней таблице. Но строки в дочерней таблице должны быть удалены перед соответствующими строками в родительской таблице. FAV_PROG_LANG='Java'
строк в дочерней таблице, то есть всех вундеркиндов с FAV_PROG_LANG='Java'
, делает невозможным впоследствии удаление всех соответствующих лиц, так как строки вундеркиндов уже были удалены. Решением этой проблемы является временная таблица, которая сначала собирает все идентификаторы строк, которые должны быть удалены. Когда все идентификаторы известны, эту информацию можно использовать для удаления строк сначала из таблицы geek, а затем из таблицы person.
Сгенерированные операторы SQL выше, конечно, не зависят от использования API критериев. Использование подхода JPQL приводит к тому же сгенерированному SQL:
01
02
03
04
05
06
07
08
09
10
11
12
|
EntityTransaction transaction = null ; try { transaction = entityManager.getTransaction(); transaction.begin(); int update = entityManager.createQuery( "delete from Geek g where g.favouriteProgrammingLanguage = :lang" ).setParameter( "lang" , "Java" ).executeUpdate(); LOGGER.info( "Deleted " + update + " rows." ); transaction.commit(); } catch (Exception e) { if (transaction != null && transaction.isActive()) { transaction.rollback(); } } |
Когда мы меняем стратегию наследования с JOINED
на SINGLE_TABLE
, сгенерированные операторы SQL также изменяются на один (здесь столбцом дискриминатора является DTYPE
):
1
2
3
4
5
6
|
delete from T_PERSON where DTYPE= 'Geek' and FAV_PROG_LANG=? |
Вывод
Новые дополнения к API-интерфейсу критериев для удаления и обновления позволяют создавать операторы SQL без необходимости объединения строк. Но имейте в виду, что массовые удаления из иерархии наследования могут заставить базовый ORM использовать временные таблицы для составления списка строк, которые необходимо удалить заранее.
Ссылка: | В соответствии с критериями JPA 2.1 удаление / обновление и временные таблицы в Hibernate от нашего партнера по JCG Мартина Мойса из блога Martin’s Developer World . |