Поддержка Querydsl в Spring Data Commons

В статье, посвящённой автоматической генерации запросов в Spring Data Commons, я упоминал о двух проблемах такого подхода: статичность запросов и одновременное отсутствие типобезопасности.  Одно из решений этой проблемы — библиотека Querydsl, которая позволяет строить запросы к данным (кстати не только JPA запросы) используя java в качестве языка описания запросов.

Подготовка

Querydsl своей реализацией отчасти похож на JPA Metamodel. Сущности, определённые в проекте, сканируются и для них создаются вспомогательные классы. Поэтому, помимо добавления артефакта Querydsl в зависимости, требуется так же добавить и плагин для генерации вспомогательных классов.

<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>
<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 или любого из его наследников всё равно является обязательным.

public interface PassportRepository extends CrudRepository<Passport, Long>, QueryDslPredicateExecutor<Passport> { }
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 настолько просто, что достаточно показать пример и сразу всё станет ясно:

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 великое множество и все они перечислены в документации. Я лишь отмечу, что их достаточно для любого практического применения:

assertFalse(personRepository.exists(qPerson.firstName.endsWith("fail")));

Обращение к полям классов, на которые ссылается сущность, реализуется весьма интуитивно:

QPassport qPassport = QPassport.passport;
Predicate passportPersonQuery = qPassport.owner.lastName.startsWith("Te");


passportRepository.findAll(passportPersonQuery)
    .forEach(System.out::println);

Кроме того, условия можно комбинировать, использовать объединения и так далее:

Predicate passportPersonQuery = qPassport.owner.lastName.startsWith("Te").and(qPassport.series.contains("A"));

Метод and(), равно как методы or(), any() и другие, принимает в себя любой объект типа Predicate, что позволяет изменять части запроса во время исполнения.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *