Использование QuerydslPredicateExecutor

Spring Data репозиторий можно расширить интерфейсом QuerydslPredicateExecutor. Нужен он для того, чтобы задать предикат, то есть условие для выбора строк в одном месте.

Введение в QueryDSL тут.

Вместо того, чтобы писать многочисленные методы, которые выбирают записи по разнообразным критериям:

findByField1(...)
findByField1andField2(...)
findByField1Containing(...)
и так далее, в зависимости от условий задачи

можно использовать метод интерфейса QuerydslPredicateExecutor, который принимает  предикат (то есть условие). И формировать это условие динамически.

Интерфейс QuerydslPredicateExecutor

Вообще в интерфейсе QuerydslPredicateExecutor есть даже несколько методов. В частности, для выбора множеств строк, одной строки, постраничной выборки и т.д.:

public interface QuerydslPredicateExecutor<T> {

    Optional<T> findOne(Predicate predicate);
    Iterable<T> findAll(Predicate predicate);
    Iterable<T> findAll(Predicate predicate, Sort sort);
    Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
    Iterable<T> findAll(OrderSpecifier<?>... orders);
    Page<T> findAll(Predicate predicate, Pageable pageable);
    long count(Predicate predicate);
    boolean exists(Predicate predicate);
}

Их объединяет то, что условия выборки задаются в аргументе Predicate.

Ниже рассмотрим, как составлять этот предикат.

Пример использования

Пусть у нас есть база, состоящая из двух таблиц: animal и category. Таблица animal имеет внешний ключ на category.

таблицы animal и category
таблицы animal и category

Соответственно JPA-сущности  Animal и Category находятся в отношении ManyToOne.

Сущности Animal и Category 

Класс Animal:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Animal {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    private String name;

    @ManyToOne
    private Category category;
}

Класс Category:

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    private String name;
}

Ниже рассмотрим AnimalRepository для сущности Animal. Только теперь он будет расширять не только JpaRepository, но и QuerydslPredicateExecutor.

AnimalRepository extends QuerydslPredicateExecutor

Итак, чтобы использовать QuerydslPredicateExecutor, унаследуем AnimalRepository от него:

public interface AnimalRepository extends JpaRepository<Animal, Long>, QuerydslPredicateExecutor {

}

Составляем предикаты

Методы QuerydslPredicateExecutor уже перечислены выше. Ради краткости возьмем только один метод:

Iterable<T> findAll(Predicate predicate);

И составим предикаты для него. Метод подойдет для получения нижеперечисленных выборок.

А нужны пусть такие выборки:

  • животные, относящиеся к категории с названием «home»;
  • животные, относящиеся к категории с названием «home» или «bird», в названии животного должны быть буква «g»;
  • животные, у которых название заканчивается на букву «t»;

Для них можно написать три метода в AnimalRepository (запросы, образованные от имени метода):

public interface AnimalRepository extends JpaRepository<Animal, Long>, QuerydslPredicateExecutor {

   
    List<Animal> findAllByCategoryName(String categoryName);
    List<Animal> findAllByCategoryNameInAndNameContaining(String[] categoryNames, String str);
    List<Animal> findAllByNameEndingWith(String str);
}

Но благодаря QuerydslPredicateExecutor  можно методы не писать, а использовать один метод findAll(Predicate) интерфейса QuerydslPredicateExecutor. То есть перенести условие из имени метода в аргумент-предикат.

Итак, вместо findAllByCategoryName() составляем предикат Predicate:

//животные, относящиеся к категории с названием "home" 

QAnimal animal=QAnimal.animal;       
Predicate predicate = animal.category.name.eq("home");

И вызываем метод:

Iterable<Animal> animals= animalRepository.findAll(predicate);
Откуда взялся класс QAnimal написано ниже — его генерирует специальный Maven плагин.

Далее, вместо findAllByCategoryNameInAndNameContaining():

//животные, относящиеся к категории с названием "home" или "bird", в названии животного должны быть буква "g"

QAnimal animal=QAnimal.animal;

Iterable<Animal> animals = animalRepository.findAll(
                                            animal.category.name.in("home", "bird")
                                            and(animal.name.contains("g"))
                                           );

Наконец, вместо метода findAllByNameEndingWith() пишем:

//животные, у которых название заканчивается на букву "t"

QAnimal animal = QAnimal.animal;

Iterable animals = animalRepository.findAll(animal.name.endsWith("t"));

QAnimal, QCategory — классы, которые генерируются из JPA-сущностей с помощью Maven-плагина.

Чтобы они появились, нужно добавить в проект некоторые Maven-зависимости.

Maven-зависимости

Они необходимы для работы с Querydsl независимо от того, составляем ли мы только предикаты для методов интерфейса QuerydslPredicateExecutor. Или же полноценно работаем с Querydsl (т.е. составляем любые запросы).

<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>4.4.0</version>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>4.4.0</version>
</dependency>

Кроме того,  в список плагинов нужно добавить следующий плагин.

Плагин

<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</outputDirectory>
                        <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

Теперь если на вкладке Maven в Intellij Idea выполнить compile, то появятся сгенерированные исходные классы QAnimal.java и QCategory.java:

Их мы и используем в примерах выше.

Также скомпилированные классы:

Q-classes
Q-classes

Итоги

Код примера доступен на GitHub.

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

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