Encodera

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

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

Оглавление

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'

Вызов без текста работает нормально, поэтому ошибка может пережить проверку. Для запросов, которые надолго остаются в коде, используйте метамодель.

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

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