Запросы: поиск, сортировка, страницы
listAll() возвращает всю таблицу, а блогу нужно больше: искать посты по заголовку, показывать свежие первыми, отдавать список по страницам. Panache принимает запросы на языке HQL — это SQL, в котором вместо таблиц и столбцов пишут классы и поля. Для простых случаев хватает короткой записи — упрощённого запроса.
Упрощённые запросы
| Запись | Что выполнится |
|---|---|
Post.list("title", "Привет") | where title = 'Привет' |
Post.list("title like ?1", "%Quarkus%") | Условие с позиционным параметром |
Post.list("title = :t", Parameters.with("t", "Привет")) | Условие с именованным параметром |
Post.count("title", "Привет") | Число подходящих строк |
Post.find("order by createdAt desc") | Только сортировка |
Методы list сразу возвращают List, а find — объект запроса, к которому можно добавить страницы. Значения всегда передают параметрами (?1, :t), а не склеивают в строку: так нет риска SQL-инъекции.
Запросы в классе сущности
В стиле активной записи свои запросы оформляют статическими методами сущности:
// src/main/java/com/example/blog/Post.java
@Entity
public class Post extends PanacheEntity {
// ... поля title, body, createdAt
public static List<Post> latest(int page, int size) {
return findAll(Sort.by("createdAt").descending())
.page(Page.of(page, size))
.list();
}
public static List<Post> search(String text) {
return list("lower(title) like ?1", Sort.by("title"), "%" + text.toLowerCase() + "%");
}
public static boolean titleExists(String title) {
return count("title", title) > 0;
}
}Импорты — io.quarkus.panache.common.Sort, io.quarkus.panache.common.Page и java.util.List. Sort.by("createdAt").descending() сортирует от новых к старым. Page.of(page, size) берёт страницу номер page (счёт с нуля) по size записей. lower(title) делает поиск нечувствительным к регистру.
Ресурс со страницами и поиском
Размер страницы берём из BlogConfig — настройки blog.page-size:
// src/main/java/com/example/blog/PostResource.java
@Inject
BlogConfig config;
@GET
public List<Post> list(@QueryParam("search") String search,
@QueryParam("page") @DefaultValue("0") int page) {
if (search != null) {
return Post.search(search);
}
return Post.latest(page, config.pageSize());
}
@GET
@Path("/count")
public long count() {
return Post.count();
}Путь /count — отдельный эндпоинт с общим числом постов. Quarkus не спутает его с /{id}: точное совпадение пути важнее шаблона.
Проверка
Поставьте в application.properties значение blog.page-size=2, создайте ещё два поста через POST /posts и запросите страницы:
curl "http://localhost:8080/posts?page=0"
curl "http://localhost:8080/posts?page=1"
curl "http://localhost:8080/posts?search=QUARKUS"
curl http://localhost:8080/posts/countВывод сокращён до заголовков:
"title":"Часть 2" "title":"Часть 1"
"title":"Режим разработки" "title":"Привет, Quarkus"
[{"id":1,"title":"Привет, Quarkus", ...}]
4
На нулевой странице — два новых поста, на первой — два старых. Поиск нашёл пост, хотя запрос был в верхнем регистре. Верните blog.page-size=10.
Когда упрощённого запроса мало
Если начало строки — не select, from или order by, Panache сам дописывает from Post where. Полный запрос тоже можно передать: Post.find("from Post p where p.createdAt > ?1", date). Для сложных отчётов есть обычный EntityManager из Jakarta Persistence — его можно внедрить через @Inject.
Частая ошибка
В запросах пишут имена полей класса, а не столбцов таблицы. Если ошибиться в имени, например Post.list("titel", "x"), клиент получит 500, а в логе будет:
ERROR [io.quarkus.vertx.http.runtime.QuarkusErrorHandler] HTTP Request to /posts failed, error id: ...: org.hibernate.query.SemanticException: Could not interpret path expression 'titel'
Ошибка возникает только при выполнении запроса, а не при старте, потому что запрос — обычная строка. Сверяйте имена в запросе с полями сущности.