Что такое JPA Criteria API и каковы его преимущества?

«Что такое JPA Criteria API и каковы его преимущества?» — вопрос из категории Hibernate, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

JPA Criteria API — это типобезопасный, объектно-ориентированный способ динамического построения запросов к базе данных в Java. Вместо строк JPQL/SQL запрос конструируется вызовом методов Java.

Ключевые преимущества:

  1. Типобезопасность: Ошибки (например, опечатки в именах полей) обнаруживаются на этапе компиляции, а не в runtime.
  2. Защита от SQL-инъекций: Поскольку запрос строится через API, риск инъекций практически исключен.
  3. Динамические запросы: Идеально подходит для построения запросов с переменным набором условий (например, в сложных фильтрах).
  4. Рефакторинг: IDE может автоматически переименовывать поля сущностей, используемые в запросах.

Основные компоненты:

  • CriteriaBuilder — фабрика для создания всех частей запроса (where, select, orderBy).
  • CriteriaQuery<T> — объект, представляющий запрос. Определяет тип результата (T).
  • Root<T> — корневой элемент запроса, представляет сущность, от которой строится запрос.
  • Predicate — условие для предложения WHERE. Условия можно комбинировать (and, or).

Пример: Поиск сотрудников с зарплатой выше заданной и сортировкой.

EntityManager em = ... // получение EntityManager
CriteriaBuilder cb = em.getCriteriaBuilder();

// 1. Создаем запрос, который вернет список Employee
CriteriaQuery<Employee> query = cb.createQuery(Employee.class);

// 2. Определяем корневую сущность запроса (FROM Employee)
Root<Employee> employee = query.from(Employee.class);

// 3. Строим предикат для условия WHERE (salary > minSalary)
ParameterExpression<Double> minSalaryParam = cb.parameter(Double.class);
Predicate salaryCondition = cb.gt(employee.get("salary"), minSalaryParam);

// 4. Собираем запрос
query.select(employee)
     .where(salaryCondition)
     .orderBy(cb.desc(employee.get("salary"))); // ORDER BY salary DESC

// 5. Создаем TypedQuery, устанавливаем параметр и выполняем
TypedQuery<Employee> typedQuery = em.createQuery(query);
typedQuery.setParameter(minSalaryParam, 100000.0);
List<Employee> resultList = typedQuery.getResultList();

Сложный пример с динамическими условиями:

List<Predicate> predicates = new ArrayList<>();
if (departmentName != null) {
    predicates.add(cb.equal(employee.get("department").get("name"), departmentName));
}
if (minExperience != null) {
    predicates.add(cb.ge(employee.get("experienceYears"), minExperience));
}
// Объединяем все условия через AND
query.where(cb.and(predicates.toArray(new Predicate[0])));

Когда использовать: Выбирайте Criteria API для сложных, изменчивых запросов, формируемых в runtime. Для простых статических запросов предпочтительнее использовать именованные запросы (@NamedQuery) или строки JPQL.