Знакомство с Criteria API

В Hibernate есть два способа написания запросов к базе:

  • Hibernate Query Language
  • Criteria API

С первым ты уже давно познакомился, настало время познакомиться с Criteria API. Это очень мощный инструмент, в какой-то момент он был даже популярнее HQL. Сейчас он уже не такой популярный, но для некоторых задач он определенно будет лучшим решением чем HQL.

В любом случае, нельзя изучать Hibernate и не познакомиться с Criteria API. Давай напишем небольшой пример, а потом его разберем. Например, запросим всех сотрудников (Employee) из базы. Вот что у нас выйдет:

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Employee> critQuery = builder.createQuery(Employee.class);

Root<Employee> root = critQuery.from(Employee.class);
critQuery.select(root);

Query<Employee> query = session.createQuery(critQuery);
List<Employee> results = query.getResultList();

Выглядит сложновато. Давай для сравнения запишем тот же запрос на HQL:

String hqlQuery = "from Employee";

Query<Employee> query = session.createQuery(hqlQuery);
List<Employee> results = query.getResultList();

Обрати внимание, что две последних строки у обоих примеров практически идентичны: мы создаем объект Query и с его помощью получаем List. Это намекает на то, что и остальные строки делают что-то идентичное.

Посмотри на строки 3 и 4 первого примера:

Root<Employee> root = critQuery.from(Employee.class);
critQuery.select(root);

Давай запишем их в одну строку:

critQuery.select(critQuery.from(Employee.class));

Ничего не напоминает? А если раскрасить немного по-другому:

critQuery.select(critQuery.from(Employee.class));

Да, это такое хитрое конструирование запроса SELECT FROM.

Примеры работы с Criteria API

Для лучшего понимания я просто приведу несколько примеров.

Запрос 1. Получить всех сотрудников с зарплатой выше 10 тысяч:

critQuery.select(critQuery.from(Employee.class)).where(builder.gt(root.get("salary"), 10000));

Запрос 2. Получить всех сотрудников с зарплатой меньше 50 тысяч:

critQuery.select(critQuery.from(Employee.class)).where(builder.lt(root.get("salary"), 50000));

Запрос 3. Получить всех сотрудников, должность которых содержит слово "тест":

critQuery.select(critQuery.from(Employee.class)).where(builder.like(root.get("occupation"), "%тест%"));

Запрос 4. Получить всех сотрудников с зарплатой от 10 до 50 тысяч:

critQuery.select(critQuery.from(Employee.class)).where(builder.between(root.get("salary"), 10000, 50000));

Запрос 5. Получить всех сотрудников, у которых имя равно null:

critQuery.select(critQuery.from(Employee.class)).where(builder.isNull(root.get("name")));

Запрос 6. Получить всех сотрудников, у которых имя не равно null:

critQuery.select(critQuery.from(Employee.class)).where(builder.isNotNull(root.get("name")));

Это просто такой хитрый способ конструировать запрос:

  • Сначала ты получаешь объект CriteriaBuilder.
  • Затем с его помощью создаешь объект CriteriaQuery.
  • Затем начинаешь добавлять к нему части с помощью CriteriaQuery и CriteriaBuilder.

Именно таким образом ты можешь задать параметры для:

  • SELECT
  • FROM
  • WHERE

Также с помощью CriteriaBuilder ты можешь конструировать различные условия для WHERE.

Продвинутая работа с Criteria API

С помощью Criteria API можно сконструировать запрос любой сложности. И это отличная новость. Например, ты хочешь сложное условие для WHERE. Вот как это можно сделать:

Predicate greaterThan = builder.gt(root.get("salary"), 1000);
Predicate testers = builder.like(root.get("occupation"), "тест%");

critQuery.select(critQuery.from(Employee.class)).where(builder.or(greaterThan, testers));

Если ты хочешь написать AND вместо OR, то тебе нужно поменять только последнюю строчку:

critQuery.select(critQuery.from(Employee.class)).where(builder.and(greaterThan, testers));

Все на самом деле очень просто. Давай я приведу таблицу с несколькими сравнениями:

SQL Метод Полная запись
a < b lt(a, b) builder.lt(a, b)
a > b gt(a, b) builder.gt(a, b)
a OR b or(a, b) builder.or(a, b)
a AND b and(a, b) builder.and(a, b)
a LIKE b like(a, b) builder.like(a, b)
a BETWEEN (c, d) between(a, c, d) builder.between(a, c, d)
a IS NULL isNull(a) builder.isNull(a)
a IS NOT NULL isNotNull(a) builder.isNotNull(a)

Все просто, неправда ли?

А как нам добавить сортировку в запрос? Очень просто:

critQuery.select( critQuery.from(Employee.class) );
critQuery.where( builder.and(greaterThan, testers) );
critQuery.orderBy( builder.asc(root.get("salary"), builder.desc(root.get("joinDate") )

Ты просто вызываешь у объекта CrteriaQuery метод orderBy() и передаешь в него нужные параметры.

Вот как этот же запрос будет выглядеть на HQL. Сравни:

select * from Employee
where (…) and (…)
order by 'salary' asc, 'joinDate' desc

Только нужно запомнить 3 вещи:

  • Ключевые операторы типа SELECT, FROM, WHERE вызываются у объекта CriteriaQuery.
  • Вспомогательные операторы типа AND, OR, DESC вызываются у объекта CriteriaBuilder.
  • Имена полей берутся через get() у объекта Root.