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

Ответ

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.

Ответ 18+ 🔞

А, ну вот, смотри, этот ваш JPA Criteria API — это такая штука, чтобы запросы к базе строить, но не строками, а как нормальные люди, методами джавы вызывать. Типа чтоб IDE помогала, а не ты сам с опечатками воевал.

Ну и в чём прикол, спросишь?

  1. Типобезопасность, ёпта! Если название поля в сущности опечатаешь — компилятор сразу тебя за шкирку: «Куда прешь, мудила? Такого поля нет!». А не как с JPQL строками — получишь ошибку только когда уже всё запустишь и накосячишь.
  2. Про инъекции забудь. Тут нихуя не вставишь лишнего, всё через API.
  3. Динамика — его конёк. Нужен сложный фильтр, где условия могут быть, а могут и не быть? Вот тут он охуенно рулит. Строками JPQL эту хуйню собирать — просто пиздец, проще на SQL переписать.
  4. Рефакторинг любишь? Переименовал поле в сущности — IDE сама везде в коде запросов поправит. Красота, а не жизнь.

Из чего этот зверь состоит, блядь?

  • CriteriaBuilder — главный по тарелочкам. От него всё пляшет: условия, сортировки, выборки. Фабрика, короче.
  • CriteriaQuery<T> — это сам запрос в сборе. В <T> пишешь, что хочешь получить в ответе.
  • Root<T> — корень, начало начал. От какой сущности запрос пойдёт (типа FROM Employee).
  • Predicate — это кусок условия для WHERE. Их можно как угодно склеивать (and, or).

Смотри, как это выглядит на практике. Найдём всех, кто получает больше какой-то суммы:

EntityManager em = ... // достали EntityManager, это твои проблемы как
CriteriaBuilder cb = em.getCriteriaBuilder(); // получили строителя

// 1. Говорим: "Хочу запрос, который вернёт мне Employee"
CriteriaQuery<Employee> query = cb.createQuery(Employee.class);

// 2. Говорим: "Берём за основу сущность Employee, блядь"
Root<Employee> employee = query.from(Employee.class);

// 3. Лепим условие: 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"))); // от больших зп к меньшим

// 5. Превращаем в рабочий запрос, подставляем значение и поехали
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 и суём в WHERE
query.where(cb.and(predicates.toArray(new Predicate[0])));

Вот видишь? Никакого пиздеца со склеиванием строк. Всё чисто, типобезопасно и красиво.

Так когда же его использовать, а? Да когда запрос — хуй пойми что, и условия в нём могут появляться и исчезать как мудаки в очереди. Для простых, раз и навсегда заданных запросов — используй @NamedQuery или обычный JPQL, не еби себе мозг. А вот для сложной динамической хуйни — это твой лучший друг, хоть и выглядит страшновато поначалу.