Запросы в Jakarta Data
Встроенных методов CrudRepository хватает для работы с одной записью. Для поиска, фильтров и страниц в интерфейс репозитория добавляют свои методы — снова без реализации. Jakarta Data понимает, какой запрос нужен, одним из трёх способов: по имени метода, по аннотации @Find с параметрами или по тексту запроса в @Query. Все примеры урока — дополнения к src/main/java/com/example/blog/Posts.java из прошлого урока.
Запросы по имени метода
Имя метода собирается из префикса, слова By, поля сущности и условия:
// src/main/java/com/example/blog/Posts.java (фрагмент)
List<Post> findByTitleIgnoreCaseContains(String text);
long countByCreatedAtGreaterThan(LocalDate date);Первый метод ищет записи, в заголовке которых есть text без учёта регистра, второй считает записи новее даты. Ошибка в имени поля обнаружится при первом вызове метода.
| Часть имени | Примеры | Значение |
|---|---|---|
| Префикс | find, count, exists, delete | Найти, посчитать, проверить, удалить |
| Условие | Contains, StartsWith, Like, In, Null | Подстрока, начало, шаблон, список, пусто |
| Сравнение | GreaterThan, LessThanEqual, Between | Больше, не больше, диапазон |
| Связки | And, Or, Not, IgnoreCase | Несколько условий, отрицание, без регистра |
| Порядок | OrderByTitleAsc, OrderByIdDesc | Сортировка результата |
@Find и @By
Длинные имена неудобно читать. Метод с @Find называют как угодно, а условия задают параметрами:
// src/main/java/com/example/blog/Posts.java (фрагмент)
@Find
@OrderBy("title")
List<Post> publishedOn(@By("createdAt") LocalDate date);Метод вернёт записи за указанный день, отсортированные по заголовку. @By связывает параметр с полем: Maven по умолчанию не сохраняет имена параметров в классе, поэтому поле называют явно.
@Query и JDQL
Для сложных условий есть @Query. Запрос пишут на JDQL (Jakarta Data Query Language) — упрощённом JPQL, где можно не писать SELECT и FROM, если запрос идёт к сущности репозитория:
// src/main/java/com/example/blog/Posts.java (фрагмент)
@Query("where lower(title) like :pattern or lower(body) like :pattern order by id")
List<Post> search(@Param("pattern") String pattern);Метод ищет текст и в заголовке, и в тексте записи. @Param задаёт имя параметра :pattern.
Сортировка и страницы
Для постраничного вывода не нужен новый метод: у BasicRepository есть findAll(PageRequest, Order). PageRequest — номер страницы (с 1) и её размер, Order — порядок сортировки из одного или нескольких Sort. Результат — Page: записи страницы и сведения о всех страницах. Для ответа создайте запись:
// src/main/java/com/example/blog/PostPage.java
package com.example.blog;
import java.util.List;
public record PostPage(List<Post> items, long page, long totalPages, long totalPosts) {
}Ресурс
Добавьте в PostV2Resource методы и импорты jakarta.data.Order, jakarta.data.Sort, jakarta.data.page.Page, jakarta.data.page.PageRequest, jakarta.ws.rs.QueryParam, jakarta.ws.rs.DefaultValue:
// src/main/java/com/example/blog/PostV2Resource.java (фрагмент)
@GET
@Path("/search")
public List<Post> search(@QueryParam("q") @DefaultValue("") String q) {
return posts.findByTitleIgnoreCaseContains(q);
}
@GET
@Path("/text")
public List<Post> text(@QueryParam("q") @DefaultValue("") String q) {
return posts.search("%" + q.toLowerCase() + "%");
}
@GET
@Path("/day/{date}")
public List<Post> day(@PathParam("date") String date) {
return posts.publishedOn(LocalDate.parse(date));
}
@GET
@Path("/count")
public long countAfter(@QueryParam("after") String date) {
return posts.countByCreatedAtGreaterThan(LocalDate.parse(date));
}
@GET
@Path("/page")
public PostPage page(@QueryParam("page") @DefaultValue("1") long page,
@QueryParam("size") @DefaultValue("2") int size) {
Page<Post> result = posts.findAll(
PageRequest.ofPage(page, size, true),
Order.by(Sort.desc("createdAt"), Sort.asc("id")));
return new PostPage(result.content(), page, result.totalPages(), result.totalElements());
}PageRequest.ofPage(page, size, true) — третий аргумент просит посчитать общее число записей, без него totalElements() недоступен. Sort.desc("createdAt") ставит новые записи первыми, а Sort.asc("id") упорядочивает записи одного дня.
Проверка
Добавьте через POST /api/v2/posts ещё записи «Методы запросов» и «Постраничный вывод», чтобы их стало пять. Вместо 2026-10-04 подставьте сегодняшнюю дату:
curl "http://localhost:8080/blog/api/v2/posts/search?q=data"
curl "http://localhost:8080/blog/api/v2/posts/text?q=запрос"
curl http://localhost:8080/blog/api/v2/posts/day/2026-10-04
curl "http://localhost:8080/blog/api/v2/posts/count?after=2026-10-01"
curl "http://localhost:8080/blog/api/v2/posts/page?page=3&size=2"[{"body":"Репозитории без лишнего кода.","createdAt":"2026-10-04","id":3,"title":"Jakarta Data"}]
[{"body":"Как запрос попадает в наш код.","createdAt":"2026-10-02","id":2,"title":"Сервлеты и фильтры"},{"body":"findBy, Find и Query.","createdAt":"2026-10-04","id":4,"title":"Методы запросов"}]
[{"body":"Репозитории без лишнего кода.","createdAt":"2026-10-04","id":3,"title":"Jakarta Data"},{"body":"findBy, Find и Query.","createdAt":"2026-10-04","id":4,"title":"Методы запросов"},{"body":"Страницы записей через PageRequest.","createdAt":"2026-10-04","id":5,"title":"Постраничный вывод"}]
4
{"items":[{"body":"Первая запись блога.","createdAt":"2026-10-01","id":1,"title":"Привет, Jakarta EE"}],"page":3,"totalPages":3,"totalPosts":5}
Поиск по data нашёл «Jakarta Data» несмотря на регистр, text — слово в тексте одной записи и в заголовке другой. На третьей странице осталась одна, самая старая запись.
Частая ошибка
Взять имя метода из Spring Data, например countByCreatedAtAfter. Слова After в Jakarta Data нет, и вызов метода падает:
CWWKD1091E: An entity attribute named CreatedAtAfter is not found on the com.example.blog.Post entity class for the countByCreatedAtAfter method of the com.example.blog.Posts repository interface.
Сервер принял CreatedAtAfter за имя поля. Используйте GreaterThan и LessThan. Порядок слов тоже важен: IgnoreCase стоит сразу после поля, поэтому findByTitleContainsIgnoreCase не сработает, а findByTitleIgnoreCaseContains — да.