Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Запросы в 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 — да.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.