0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Выборка списка сущностей в API почти всегда подразумевает какую-то фильтрацию данных. Например, список постов конкретного автора, за какой-то срок или только опубликованных. А если данных много даже после фильтрации, то они отдаются постранично. Реализовать необходимую логику можно с помощью:</p>
1
<p>Выборка списка сущностей в API почти всегда подразумевает какую-то фильтрацию данных. Например, список постов конкретного автора, за какой-то срок или только опубликованных. А если данных много даже после фильтрации, то они отдаются постранично. Реализовать необходимую логику можно с помощью:</p>
2
<ul><li>Автоматической генерации методов в JPA Repository в простых случаях</li>
2
<ul><li>Автоматической генерации методов в JPA Repository в простых случаях</li>
3
<li>JPA Specifications в более сложных случаях</li>
3
<li>JPA Specifications в более сложных случаях</li>
4
<li><a>QueryDSL</a>или других сторонних библиотек</li>
4
<li><a>QueryDSL</a>или других сторонних библиотек</li>
5
</ul><p>В этом уроке мы поговорим о JPA Specifications - механизме, который позволяет динамически собирать сложные запросы в рамках одного метода без необходимости создавать новый метод под каждое условие выборки.</p>
5
</ul><p>В этом уроке мы поговорим о JPA Specifications - механизме, который позволяет динамически собирать сложные запросы в рамках одного метода без необходимости создавать новый метод под каждое условие выборки.</p>
6
<h2>JPA Specifications</h2>
6
<h2>JPA Specifications</h2>
7
<p>Для реализации этого механизма нужно выполнить следующие шаги:</p>
7
<p>Для реализации этого механизма нужно выполнить следующие шаги:</p>
8
<ul><li>Добавить интерфейс JpaSpecificationExecutor в репозиторий</li>
8
<ul><li>Добавить интерфейс JpaSpecificationExecutor в репозиторий</li>
9
<li>Создать DTO для параметров запроса, который будет использоваться для фильтрации</li>
9
<li>Создать DTO для параметров запроса, который будет использоваться для фильтрации</li>
10
<li>Описать спецификацию для конкретной сущности</li>
10
<li>Описать спецификацию для конкретной сущности</li>
11
<li>Внедрить использование спецификации в контроллере</li>
11
<li>Внедрить использование спецификации в контроллере</li>
12
</ul><p>Все это мы будем добавлять для сущности Post. Ее код выглядит так:</p>
12
</ul><p>Все это мы будем добавлять для сущности Post. Ее код выглядит так:</p>
13
<h3>Обновление репозитория</h3>
13
<h3>Обновление репозитория</h3>
14
<p>Для работы динамического фильтра на базе спецификации нужно добавить интерфейс JpaSpecificationExecutor. В нем описаны методы для работы с данными на основе спецификации:</p>
14
<p>Для работы динамического фильтра на базе спецификации нужно добавить интерфейс JpaSpecificationExecutor. В нем описаны методы для работы с данными на основе спецификации:</p>
15
<p>Метод findAll интерфейса JpaSpecificationExecutor возвращает страницу с постами на основе переданной спецификации. Вторым параметром метод принимает Pageable, который определяет смещение и количество данных в части LIMIT. Это хорошая практика, потому что возвращение всех данных почти всегда приводит к проблемам с производительностью:</p>
15
<p>Метод findAll интерфейса JpaSpecificationExecutor возвращает страницу с постами на основе переданной спецификации. Вторым параметром метод принимает Pageable, который определяет смещение и количество данных в части LIMIT. Это хорошая практика, потому что возвращение всех данных почти всегда приводит к проблемам с производительностью:</p>
16
<h3>Создание DTO</h3>
16
<h3>Создание DTO</h3>
17
<p>Обычно фильтры состоят больше, чем из одного параметра. В этом случае неудобно получать каждый параметр по отдельности. Гораздо проще создать для них DTO, который будет создан при вызове метода контроллера. Spring Boot автоматически сопоставляет параметры запроса со свойствами объекта и заполняет их, если они переданы:</p>
17
<p>Обычно фильтры состоят больше, чем из одного параметра. В этом случае неудобно получать каждый параметр по отдельности. Гораздо проще создать для них DTO, который будет создан при вызове метода контроллера. Spring Boot автоматически сопоставляет параметры запроса со свойствами объекта и заполняет их, если они переданы:</p>
18
<p>Сам DTO включает те параметры, по которым мы хотим фильтровать. В нашем случае это будет:</p>
18
<p>Сам DTO включает те параметры, по которым мы хотим фильтровать. В нашем случае это будет:</p>
19
<ul><li>Параметр authorId выбирает посты по автору</li>
19
<ul><li>Параметр authorId выбирает посты по автору</li>
20
<li>Параметр nameCont выбирает посты по вхождению в название поста (здесь<em>Cont</em>обозначает<em>contain</em>- "содержать")</li>
20
<li>Параметр nameCont выбирает посты по вхождению в название поста (здесь<em>Cont</em>обозначает<em>contain</em>- "содержать")</li>
21
<li>Параметр createdAtGt выбирает посты, появившиеся позже указанной даты (здесь<em>gt</em>обозначает<em>greater than</em>- "более чем")</li>
21
<li>Параметр createdAtGt выбирает посты, появившиеся позже указанной даты (здесь<em>gt</em>обозначает<em>greater than</em>- "более чем")</li>
22
<li>Параметр createdAtLt он выбирает посты, появившиеся раньше указанной даты (здесь<em>lt</em>обозначает<em>lesser than</em>- "менее чем")</li>
22
<li>Параметр createdAtLt он выбирает посты, появившиеся раньше указанной даты (здесь<em>lt</em>обозначает<em>lesser than</em>- "менее чем")</li>
23
</ul><p>Добавлять суффиксы<em>Cont</em>,<em>Gt</em>и<em>Lt</em>в название полей не обязательно. С другой стороны, это очень удобно, потому что позволяет использовать одно и то же поле несколько раз так, что сразу понятно, для чего нужен этот параметр и как он примерно работает. Ниже код соответствующего DTO:</p>
23
</ul><p>Добавлять суффиксы<em>Cont</em>,<em>Gt</em>и<em>Lt</em>в название полей не обязательно. С другой стороны, это очень удобно, потому что позволяет использовать одно и то же поле несколько раз так, что сразу понятно, для чего нужен этот параметр и как он примерно работает. Ниже код соответствующего DTO:</p>
24
<h3>Создание спецификации</h3>
24
<h3>Создание спецификации</h3>
25
<p>Спецификация работает как билдер, которому передаются различные условия фильтрации. На базе этой спецификации Spring Boot JPA выполняет генерацию SQL. Ниже один из примеров описания спецификации:</p>
25
<p>Спецификация работает как билдер, которому передаются различные условия фильтрации. На базе этой спецификации Spring Boot JPA выполняет генерацию SQL. Ниже один из примеров описания спецификации:</p>
26
<p>В методе build происходит сборка спецификации на основе переданных параметров. Каждый параметр формирует свое условие фильтрации данных. Обработка каждого параметра вынесена в свой метод для удобства. Внутри этих методов есть общая логика, связанная с проверкой наличия параметра. Если он отсутствует, то возвращается cb.conjunction(), который ни на что не влияет, но нужен для работы цепочки методов.</p>
26
<p>В методе build происходит сборка спецификации на основе переданных параметров. Каждый параметр формирует свое условие фильтрации данных. Обработка каждого параметра вынесена в свой метод для удобства. Внутри этих методов есть общая логика, связанная с проверкой наличия параметра. Если он отсутствует, то возвращается cb.conjunction(), который ни на что не влияет, но нужен для работы цепочки методов.</p>
27
<p>Спецификация представляет собой лямбда-функцию с тремя параметрами:</p>
27
<p>Спецификация представляет собой лямбда-функцию с тремя параметрами:</p>
28
<ul><li>Объект root (Root<T>), который считается представлением сущности. С помощью него мы указываем, по какому свойству нужно выполнять фильтрацию, включая обращение к свойствам зависимых сущностей</li>
28
<ul><li>Объект root (Root<T>), который считается представлением сущности. С помощью него мы указываем, по какому свойству нужно выполнять фильтрацию, включая обращение к свойствам зависимых сущностей</li>
29
<li>Объект cb (CriteriaBuilder), который предоставляет методы для создания фильтров - equal(), like() и greaterThan()</li>
29
<li>Объект cb (CriteriaBuilder), который предоставляет методы для создания фильтров - equal(), like() и greaterThan()</li>
30
<li>Объект query (CriteriaQuery<T>), который отвечает за формирование правильной структуры запроса. Еще с его помощью можно указывать используемые колонки, таблицы, условия фильтрации и сортировки данных</li>
30
<li>Объект query (CriteriaQuery<T>), который отвечает за формирование правильной структуры запроса. Еще с его помощью можно указывать используемые колонки, таблицы, условия фильтрации и сортировки данных</li>
31
</ul><h3>Использование спецификации в контроллере</h3>
31
</ul><h3>Использование спецификации в контроллере</h3>
32
<p>Что происходит в этом коде:</p>
32
<p>Что происходит в этом коде:</p>
33
<ol><li>В метод приходят параметры для фильтрации и страница, которую нужно выбрать</li>
33
<ol><li>В метод приходят параметры для фильтрации и страница, которую нужно выбрать</li>
34
<li>На основе параметров для фильтрации формируется спецификация</li>
34
<li>На основе параметров для фильтрации формируется спецификация</li>
35
<li>Выполняется выборка данных по спецификации и с учетом указанной страницы данных. Из page вычитается единица, потому что иначе получится запрос LIMIT 10 OFFSET 10 вместо LIMIT 10 OFFSET 0</li>
35
<li>Выполняется выборка данных по спецификации и с учетом указанной страницы данных. Из page вычитается единица, потому что иначе получится запрос LIMIT 10 OFFSET 10 вместо LIMIT 10 OFFSET 0</li>
36
<li>Возвращенный результат Page<Post> преобразуется в Page<PostDTO> с помощью встроенного в Page метода map(), который работает точно так же, как map() в стримах</li>
36
<li>Возвращенный результат Page<Post> преобразуется в Page<PostDTO> с помощью встроенного в Page метода map(), который работает точно так же, как map() в стримах</li>
37
</ol>
37
</ol>