Spring Data репозиторий можно расширить интерфейсом QuerydslPredicateExecutor. Нужен он для того, чтобы задать предикат, то есть условие для выбора строк в одном месте.
Вместо того, чтобы писать многочисленные методы, которые выбирают записи по разнообразным критериям:
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.

Соответственно 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);
Далее, вместо 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:

Их мы и используем в примерах выше.
Также скомпилированные классы:

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