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.