Питання 27 · Розділ 16

Що таке Criteria API і коли його використовувати

Criteria API — type-safe спосіб побудови JPQL запитів програмно. Він особливо корисний для динамічних запитів з багатьма опціональними фільтрами, де статичний JPQL був би незруч...

Мовні версії: English Russian Ukrainian

Огляд

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));

Основні компоненти

CriteriaBuilder     — фабрика для створення виразів
CriteriaQuery<T>    — сам запит
Root<T>             — корнева сутність (FROM clause)
Predicate           — умова WHERE

🟡 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]));
    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;
}

// Type-safe запит
root.get(User_.age)  // замість root.get("age")

🔴 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);
    }
}

// Використання
Specification<User> spec = Specification
    .where(UserSpecifications.hasName("John"))
    .and(UserSpecifications.hasMinAge(18));

List<User> users = userRepository.findAll(spec);

Best Practices

✅ Criteria API для динамічних запитів
✅ Metamodel для type-safety
✅ JPQL для простих статичних запитів
✅ Spring Data JPA Specification
✅ Fetch joins для пов'язаних даних

❌ Criteria API для простих запитів (надмірно)
❌ Без metamodel (string-based prone to errors)

🎯 Шпаргалка для співбесіди

Обов’язково знати:

  • Criteria API — type-safe спосіб побудови JPQL запитів програмно
  • Основні компоненти: CriteriaBuilder, CriteriaQuery, Root, Predicate
  • Найкраще для динамічних запитів з багатьма опціональними фільтрами
  • Metamodel (User_) забезпечує повну type-safety — помилки на компіляції
  • Specification pattern (Spring Data JPA) — переиспользуемі критерії
  • Для простих статичних запитів JPQL простіший і читабельніший

Пов’язані теми:

  • [[26. Що таке JPQL і чим він відрізняється від SQL]]
  • [[28. Як використовувати JOIN FETCH для вирішення проблеми N+1]]
  • [[29. Що таке projection в JPA]]
  • [[23. Як правильно використовувати @OneToMany і @ManyToOne]]