JPA Criteria API

Criteria API строит JPQL-подобный запрос объектами. Он полезен, когда набор predicates, joins или сортировок формируется динамически. Для фиксированного запроса JPQL или repository method обычно читается проще.

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Order> criteria = cb.createQuery(Order.class);
Root<Order> order = criteria.from(Order.class);
 
ParameterExpression<Status> status = cb.parameter(Status.class, "status");
 
criteria.select(order)
        .where(cb.equal(order.get("status"), status))
        .orderBy(cb.desc(order.get("createdAt")));
 
List<Order> result = entityManager
        .createQuery(criteria)
        .setParameter("status", Status.NEW)
        .setMaxResults(100)
        .getResultList();

Для compile-time проверки атрибутов нужен статический metamodel:

criteria.where(cb.equal(order.get(Order_.status), status));
criteria.orderBy(cb.desc(order.get(Order_.createdAt)));

Строка "status" без metamodel остаётся runtime-риском. Type-safe generic API не устраняет ошибку имени свойства.

Динамическая сборка

List<Predicate> filters = new ArrayList<>();
 
if (from != null) {
    filters.add(cb.greaterThanOrEqualTo(order.get(Order_.createdAt), from));
}
if (customerId != null) {
    filters.add(cb.equal(
            order.get(Order_.customer).get(Customer_.id),
            customerId
    ));
}
 
criteria.where(filters.toArray(Predicate[]::new));

Значения пользовательского ввода передаются параметрами/через API, а не конкатенацией строки SQL.

Ограничения ORM

  • Criteria генерирует запрос, но не гарантирует хороший SQL;
  • join коллекции может размножить root rows и потребовать distinct;
  • fetch join нескольких коллекций создаёт большой декартов результат;
  • fetch join и pagination часто несовместимы предсказуемо;
  • count query для страницы обычно строится отдельно;
  • Criteria само по себе не устраняет N+1.

После изменения запроса проверяю фактический SQL, план выполнения и число запросов.

На собеседовании

Когда Criteria лучше JPQL? При действительно динамическом наборе условий/joins.

Всегда ли Criteria compile-time type-safe? Нет, root.get("field") содержит строку; metamodel повышает безопасность.

Защищает ли Criteria от N+1? Нет.

Почему для pagination нужен отдельный count query? Запрос данных и подсчёт имеют разные select/fetch и требования к эффективности.

Связано: Spring Data JPA, Entity Graph, Проблема N plus 1 Select Hibernate, SQL инъекции.