Criteria API
Запрос JPQL — это строка. Это удобно, пока запрос постоянный, но форма поиска с необязательными фильтрами превращает сборку строки в кашу из if и +. Criteria API строит тот же запрос из Java-объектов: условия создаются вызовами методов и добавляются только тогда, когда нужны.
Поиск с необязательными фильтрами
Создайте класс src/main/java/com/example/blog/PostSearch.java:
// src/main/java/com/example/blog/PostSearch.java
package com.example.blog;
import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
public class PostSearch {
public static List<Post> find(EntityManager em, String text, Integer minViews) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Post> query = cb.createQuery(Post.class);
Root<Post> post = query.from(Post.class);
List<Predicate> conditions = new ArrayList<>();
if (text != null) {
conditions.add(cb.like(cb.lower(post.get("title")), "%" + text.toLowerCase() + "%"));
}
if (minViews != null) {
conditions.add(cb.ge(post.get("views"), minViews));
}
query.select(post)
.where(conditions.toArray(Predicate[]::new))
.orderBy(cb.desc(post.get("views")));
return em.createQuery(query).getResultList();
}
}| Объект | Роль |
|---|---|
CriteriaBuilder | Фабрика условий и функций: like, ge, lower, desc |
CriteriaQuery<Post> | Сам запрос, аналог select ... from ... where ... |
Root<Post> | Сущность во from; post.get("title") — её поле |
Predicate | Одно условие; несколько условий в where объединяются через and |
Вызов
// src/main/java/com/example/blog/Main.java
Seed.run(emf);
emf.runInTransaction(em -> {
IO.println("Все: " + PostSearch.find(em, null, null).stream().map(Post::getTitle).toList());
IO.println("'java': " + PostSearch.find(em, "java", null).stream().map(Post::getTitle).toList());
IO.println("'hibernate' и от 50 просмотров: " + PostSearch.find(em, "hibernate", 50).stream().map(Post::getTitle).toList());
});Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 order by p1_0.views desc
Все: [Привет, Hibernate, Типы record в Java 25, Ленивая загрузка]
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where lower(p1_0.title) like ? escape '' order by p1_0.views desc
'java': [Типы record в Java 25]
Hibernate: select p1_0.id,p1_0.author_id,p1_0.title,p1_0.views from Post p1_0 where lower(p1_0.title) like ? escape '' and p1_0.views>=? order by p1_0.views desc
'hibernate' и от 50 просмотров: [Привет, Hibernate]Один метод дал три разных SQL-запроса: без where, с одним условием и с двумя. Текст поиска ушёл в параметр, поэтому он безопасен.
Статическая метамодель
Имена полей строками — "title", "views" — проверяются только во время выполнения. Обработчик аннотаций hibernate-processor при компиляции создаёт класс Post_ с константой для каждого поля. Подключите его в pom.xml:
<!-- pom.xml, внутри <build><plugins> -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-processor</artifactId>
<version>7.4.12.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>После mvn compile в target/generated-sources/annotations появятся классы Post_, Author_ и другие. Теперь поиск пишется так:
// src/main/java/com/example/blog/PostSearch.java
conditions.add(cb.like(cb.lower(post.get(Post_.title)), "%" + text.toLowerCase() + "%"));
conditions.add(cb.ge(post.get(Post_.views), minViews));
query.orderBy(cb.desc(post.get(Post_.views)));Опечатка в Post_.titel теперь — ошибка компиляции, а переименование поля обновляет метамодель. Результат программы тот же.
Частая ошибка
Со строковыми именами опечатка обнаруживается, только когда выполняется эта ветка кода. Вызов поиска с текстом, когда в коде написано post.get("titel"):
Exception in thread "main" org.hibernate.query.sqm.PathElementException: Could not resolve attribute 'titel' of 'com.example.blog.Post'Вызов без текста работает нормально, поэтому ошибка может пережить проверку. Для запросов, которые надолго остаются в коде, используйте метамодель.