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 инъекции.