Что такое Criteria API и когда его использовать
Criteria API — type-safe способ построения JPQL запросов программно. Он особенно полезен для динамических запросов с множеством опциональных фильтров, где статический JPQL был б...
Обзор
Criteria API — type-safe способ построения JPQL запросов программно. Он особенно полезен для динамических запросов с множеством опциональных фильтров, где статический JPQL был бы неудобен.
🟢 Junior Level
Что такое Criteria API
Criteria API — type-safe способ строить JPQL запросы программно.
// JPQL — строковый запрос
"SELECT u FROM User u WHERE u.age > 18"
// Criteria API — type-safe
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
query.select(root).where(cb.gt(root.get("age"), 18));
List<User> users = entityManager.createQuery(query).getResultList();
Основные компоненты
CriteriaBuilder — фабрика для создания выражений
CriteriaQuery<T> — сам запрос
Root<T> — корневая сущность (FROM clause)
Predicate — условие WHERE
Простой пример
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
// SELECT u FROM User u WHERE u.age > 18
query.select(root)
.where(cb.greaterThan(root.get("age"), 18));
// ⚠️ String-based access — НЕ type-safe. Для type-safety используйте Metamodel:
// root.get(User_.age)
List<User> users = entityManager.createQuery(query).getResultList();
🟡 Middle Level
Когда использовать
✅ Criteria API когда:
- Динамические запросы (много опциональных фильтров)
- Type-safe нужен (компиляция ловит ошибки)
- Рефакторинг безопасен
❌ JPQL когда:
- Простые статические запросы
- Легче читать
- Меньше кода
Динамический запрос с фильтрами
public List<User> findUsers(String name, Integer minAge, String status) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> root = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null) {
predicates.add(cb.like(root.get("name"), "%" + name + "%"));
}
if (minAge != null) {
predicates.add(cb.ge(root.get("age"), minAge));
}
if (status != null) {
predicates.add(cb.equal(root.get("status"), status));
}
query.where(predicates.toArray(new Predicate[0]));
query.orderBy(cb.asc(root.get("name")));
return entityManager.createQuery(query).getResultList();
}
С Metamodel (type-safe)
// Metamodel генерируется автоматически
@StaticMetamodel(User.class)
public abstract class User_ {
public static volatile SingularAttribute<User, String> name;
public static volatile SingularAttribute<User, Integer> age;
public static volatile SingularAttribute<User, String> status;
}
// Type-safe запрос
root.get(User_.age) // вместо root.get("age")
JOIN в Criteria API
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);
Join<Order, User> userJoin = root.join("user");
query.select(root)
.where(cb.equal(userJoin.get("name"), "John"));
🔴 Senior Level
Specification pattern (Spring Data JPA)
public class UserSpecifications {
public static Specification<User> hasName(String name) {
return (root, query, cb) ->
cb.like(root.get(User_.name), "%" + name + "%");
}
public static Specification<User> hasMinAge(int minAge) {
return (root, query, cb) ->
cb.greaterThanOrEqualTo(root.get(User_.age), minAge);
}
public static Specification<User> hasStatus(String status) {
return (root, query, cb) ->
cb.equal(root.get(User_.status), status);
}
}
// Использование
Specification<User> spec = Specification
.where(UserSpecifications.hasName("John"))
.and(UserSpecifications.hasMinAge(18))
.and(UserSpecifications.hasStatus("ACTIVE"));
List<User> users = userRepository.findAll(spec);
Subqueries
// Упрощённый пример — в реальном коде subquery могут быть значительно сложнее
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> userRoot = query.from(User.class);
// Subquery
Subquery<Long> subquery = query.subquery(Long.class);
Root<Order> orderRoot = subquery.from(Order.class);
subquery.select(cb.max(orderRoot.get("total")))
.where(cb.equal(orderRoot.get("user").get("id"), userRoot.get("id")));
query.select(userRoot)
.where(cb.greaterThan(userRoot.get("totalSpent"), subquery));
Fetch joins
CriteriaQuery<Order> query = cb.createQuery(Order.class);
Root<Order> root = query.from(Order.class);
root.fetch("items", JoinType.LEFT); // FETCH JOIN
query.select(root)
.distinct(true); // убрать дубликаты
Генерация Metamodel
<!-- pom.xml -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<scope>provided</scope>
</dependency>
// Генерирует User_, Order_, и т.д. автоматически
Best Practices
✅ Criteria API для динамических запросов
✅ Metamodel для type-safety
✅ JPQL для простых статических запросов
✅ Spring Data JPA Specification
✅ Fetch joins для связанных данных
✅ Distinct для дубликатов
❌ Criteria API для простых запросов (избыточно)
❌ Без metamodel (string-based prone to errors)
❌ String concat в Criteria — риск query plan cache pollution и нечитаемость. Значения всё равно параметризуются, SQL injection маловероятен.
❌ Игнорирование distinct при fetch joins
🎯 Шпаргалка для интервью
Обязательно знать:
- Criteria API — type-safe способ построения JPQL запросов программно
- Основные компоненты: CriteriaBuilder, CriteriaQuery, Root, Predicate
- Лучше всего для динамических запросов с множеством опциональных фильтров
- Metamodel (User_) обеспечивает полную type-safety — ошибки на компиляции
- Specification pattern (Spring Data JPA) — переиспользуемые критерии
- Для простых статических запросов JPQL проще и чита
Частые уточняющие вопросы:
- Criteria API vs JPQL — когда что? Criteria API для динамических запросов (опциональные фильтры), JPQL для статических
- Зачем Metamodel? root.get(“age”) — string-based, ошибки runtime; root.get(User_.age) — type-safe, ошибки на компиляции
- Specification pattern что даёт? Композиция критериев: where(hasName).and(hasMinAge), переиспользование кода
- Criteria API поддерживает subqueries и fetch joins? Да — Subquery для подзапросов, root.fetch() для fetch joins
Красные флаги (НЕ говорить):
- «Criteria API для простых SELECT» — избыточно, JPQL проще
- «Без Metamodel — использую строки» — runtime errors при рефакторинге
- «String concat в Criteria» — query plan cache pollution, нечитаемость
- «Игнорирую distinct при fetch joins» — дубликаты в результатах
Связанные темы:
- [[26. Что такое JPQL и чем он отличается от SQL]]
- [[28. Как использовать JOIN FETCH для решения проблемы N+1]]
- [[29. Что такое projection в JPA]]
- [[23. Как правильно использовать @OneToMany и @ManyToOne]]