В статье, посвящённой автоматической генерации запросов в Spring Data Commons, я упоминал о двух проблемах такого подхода: статичность запросов и одновременное отсутствие типобезопасности. Одно из решений этой проблемы — библиотека Querydsl, которая позволяет строить запросы к данным (кстати не только JPA запросы) используя java в качестве языка описания запросов.
Подготовка
Querydsl своей реализацией отчасти похож на JPA Metamodel. Сущности, определённые в проекте, сканируются и для них создаются вспомогательные классы. Поэтому, помимо добавления артефакта Querydsl в зависимости, требуется так же добавить и плагин для генерации вспомогательных классов.
1
2
3
4
5
6
7
8
9
10
11
12
|
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<querydsl.version>4.1.4</querydsl.version>
</properties>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>
</dependencies>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<build>
<plugins>
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
|
Разумеется, надо не забыть добавить в зависимости Spring Data JPA, сам Spring и какую-нибудь реализацию JPA.
Интерфейс QueryDslPredicateExecutor
Чтобы включить поддержку Querydsl в генерируемый репозиторий, необходимо чтоб он имел в списке предков интерфейс QueryDslPredicateExecuter<T>, где T — тип сущности, с которой работает репозиторий. При этом, даже если планируется работать только с Querydsl, наличие в предках интерфейса Repository или любого из его наследников всё равно является обязательным.
1
|
public interface PassportRepository extends CrudRepository<Passport, Long>, QueryDslPredicateExecutor<Passport> { }
|
1
|
public interface PersonRepository extends CrudRepository<Person, Long>, QueryDslPredicateExecutor<Person> { }
|
Интерфейс QueryDslPredicateExecutor<T> определяет несколько методов:
- T FindOne(Predicate) — возвращает один объект, соответствующий условия
- Iterable<T> findAll(Predicate) — возвращает несколько объектов, соответствующих условию. Обратите внимание, что возвращается всегда Iterable<T>, без возможности уточнить тип
- long count(Predicate) — возвращает количество объектов в базе данных, соответствующих условию
- boolean exists(Predicate) — сообщает, есть ли в базе данных объект соответствующий условию
Все методы принимают условие Querydsl.
Предикаты в Querydsl
Написание запросов в Querydsl настолько просто, что достаточно показать пример и сразу всё станет ясно:
1
2
3
4
5
|
QPerson qPerson = QPerson.person;
Predicate personTestQuery = qPerson.firstName.eq("Test");
personRepository.findAll(personTestQuery)
.forEach(System.out::println);
|
QPerson и QPassport выше — специальные вспомогательные типы Querydsl, коорые были сгенерированы из описаний сущностией (а сами сущности я взял из вводной статьи о Spring Data Commons). Querydsl плагин автоматически создаёт классы с префиксом Q для каждого найденного им класса сущности. Внутри такого Q* класса содержится единственный глобальный объект этого класса, на основе которого уже и делаются запросы. У объекта Q* класса есть все публичные поля, которые есть у сущности, из которой он был сгенерирован. Но, в этих полях не содержится значений, у них есть методы, описывающие, какими должны быть значения поля. В примере выше я строю запрос, который требует, чтобы поле firstName сущности Person имело значение «Test».
Благодаря использованию вспомогательных классов запросы, написанные с помощью Querydsl, типобезопасны и проверяются на стадии компиляции. А создание запросов методом вызова java функций позволяет создавать динамические запросы во время исполнения.
Возможных условий в Querydsl великое множество и все они перечислены в документации. Я лишь отмечу, что их достаточно для любого практического применения:
1
|
assertFalse(personRepository.exists(qPerson.firstName.endsWith("fail")));
|
Обращение к полям классов, на которые ссылается сущность, реализуется весьма интуитивно:
1
2
3
4
5
|
QPassport qPassport = QPassport.passport;
Predicate passportPersonQuery = qPassport.owner.lastName.startsWith("Te");
passportRepository.findAll(passportPersonQuery)
.forEach(System.out::println);
|
Кроме того, условия можно комбинировать, использовать объединения и так далее:
1
|
Predicate passportPersonQuery = qPassport.owner.lastName.startsWith("Te").and(qPassport.series.contains("A"));
|
Метод and(), равно как методы or(), any() и другие, принимает в себя любой объект типа Predicate, что позволяет изменять части запроса во время исполнения.
Код примера доступен на github.