Criteria API, часть 2

Модуль 4. Работа с БД
16 уровень , 1 лекция
Открыта

Группировка и агрегирующие функции

Как делать простые запросы на Criteria API ты уже разобрался. Давай посмотрим, как делать более сложные запросы.

Например, мы хотим написать запрос, чтобы определить количество сотрудников в компании. Вот как он будет выглядеть на HQL:

select count(*) from Employee

А вот так на Criteria API:


CriteriaQuery<Long> critQuery = builder.createQuery(Long.class);
critQuery.select(builder.count(critQuery.from(Employee.class)));

Полностью весь Java-код будет выглядеть так:


CriteriaBuilder builder = session.getCriteriaBuilder();
 
CriteriaQuery<Long> critQuery = builder.createQuery(Long.class);
critQuery.select(builder.count(critQuery.from(Employee.class)));
 
Query<Long> query = session.createQuery(critQuery);
Long count = query.getSingleResult();

И он же с использованием HQL:


String hqlQuery = "select count(*) from Employee";
 
Query<Long> query = session.createQuery(hqlQuery);
Long count = query.getSingleResult();

Теперь давай попробуем посчитать среднюю зарплату в компании. Запрос на HQL будет выглядеть так:

select avg(salary) from Employee

А вот так на Criteria API:


CriteriaQuery<Double> critQuery = builder.createQuery(Double.class);
critQuery.select(builder.avg( critQuery.from(Employee.class).get("salary")));

Полностью весь Java-код будет выглядеть так:


CriteriaBuilder builder = session.getCriteriaBuilder();
 
CriteriaQuery<Double> critQuery = builder.createQuery(Double.class);
critQuery.select(builder.avg( critQuery.from(Employee.class).get("salary")));
 
Query<Double> query = session.createQuery(critQuery);
Double avgSalary = query.getSingleResult();

CriteriaUpdate

Изменять таблицу так же легко, как и получать из нее данные. Для этого у CriteriaBuilder есть специальный метод – createCriteriaUpdate(), который создает объект CriteriaUpdate<T>, обновляющий сущности в базе.

Давай повысим зарплату сотрудникам, которые получают меньше 10 тысяч. Вот как будет выглядеть этот запрос на HQL:

update Employee set salary = salary+20000 where salary<=10000

А вот так он будет выглядеть на Criteria API:


CriteriaUpdate<Employee> criteriaUpdate = builder.createCriteriaUpdate(Employee.class);
Root<Employee> root = criteriaUpdate.from(Employee.class);
criteriaUpdate.set("salary", "salary+20000");
criteriaUpdate.where(builder.lt(root.get("salary"), 10000));
 
Transaction transaction = session.beginTransaction();
session.createQuery(criteriaUpdate).executeUpdate();
transaction.commit();

CriteriaDelete

А удалять записи еще проще, чем изменять. Для этого есть специальный метод createCriteriaDelete(), который создает объект CriteriaDelete<T>.

Давай сократим всех сотрудников, которые не имеют никакой ценности: их зарплата меньше 10 тысяч. Вот как будет выглядеть этот запрос на HQL:

delete from Employee where salary<=10000

А вот так он будет выглядеть на Criteria API:


CriteriaDelete<Employee> criteriaDelete = builder.createCriteriaDelete(Employee.class);
Root<Employee> root = criteriaDelete.from(Employee.class);
criteriaDelete.where(builder.lt(root.get("salary"), 10000));
 
Transaction transaction = session.beginTransaction();
session.createQuery(criteriaDelete).executeUpdate();
transaction.commit();

Польза от Criteria API

Так в чем же польза от Criteria API? Запросы громоздкие, на HQL точно компактнее будет.

Во-первых, запросы на HQL не такие уж и короткие, если тебе нужно передавать в них параметры. Сравни:

Считаем количество сотрудников с зарплатой меньше 10 тысяч
HQL
String hqlQuery = "from Employee where salary < :sal"; Query<Employee> query = session.createQuery(hqlQuery); query.setParametr("sal", 10000); List<Employee> results = query.getResultList();
Criteria API

CriteriaBuilder builder = session.getCriteriaBuilder();
critQuery.select(critQuery.from(Employee.class)).where(builder.lt(root.get("salary"), 10000));
Query<Employee> query = session.createQuery(critQuery);
List<Employee> results = query.getResultList();

Во-вторых, очень часто бывает ситуация, когда запрос нужно сконструировать динамически. Например, у тебя на web-странице есть фильтрация сотрудников, квартир, да чего угодно. И если пользователю какой-то параметр не важен, то он его просто не указывает. Соответственно, на сервер вместо него передается null.

Вот тебе задача: отобрать сотрудников с определенной профессией (occupation), зарплатой (salary) и годом найма (YEAR (join_date)). Но если любое значение параметра равно null, то не использовать его в фильтре.

Тогда запрос на HQL будет выглядеть примерно так:


from Employee
where (occupation = :ocp)
   	and (salary = :sal)
   	and ( YEAR(join_date) = :jny)

Но работать правильно он не будет, так как мы хотим, чтобы если параметр "jny" был null, то запрос выглядел бы так:


from Employee
where (occupation = :ocp)
   	and (salary = :sal)

Можно попробовать переписать запрос с проверкой параметра на null, тогда мы получим что-то типа этого:


from Employee
where (occupation = :ocp or :ocp is null)
   	and (salary = :sal or :sal is null)
   	and ( YEAR(join_date)= :jny or :jny is null)

Видишь, как реальность усложняется? Реальность часто такая :)

А ведь фильтр можно усложнить еще сильнее. Как насчет поиска пользователей, у которых есть задачи со словом "купить"? Или пользователей, у которых есть просроченные задачи?


from Employee
where (occupation = :ocp)
   	and (salary = :sal)
   	and (YEAR(join_date) = :jny)
   	and (tasks.name like '%купить%')
   	and (tasks.deadline < curdate())

Если ты в таком запросе где-то запишешь or is null, то это не отменит join между таблицами.

Так что на практике, когда ты делаешь любой сложный фильтр по полям нескольких таблиц, Criteria API могут тебя просто выручать. Такие дела.

Более подробно можно ознакомиться в официальной документации.

1
Задача
Модуль 4. Работа с БД, 16 уровень, 1 лекция
Недоступна
Удаление через Criteria API
Используя Criteria API, в методе deleteEmployeeById удали объект Employee из базы данных по его id...
Комментарии (12)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Надежда Уровень 104 Expert
13 октября 2023
JavaRush, вы серьезно? Как можно такой бред писать? criteriaUpdate.set("salary", "salary+20000"); Для вычисления нового значения надо использовать builder.sum: Expression<Integer> newSalary = builder.sum(root.get("salary"), 20000); // Приведение типа к Path<Integer> Path<Integer> salaryPath = root.get("salary"); criteriaUpdate.set(salaryPath, newSalary); criteriaUpdate.where(builder.lt(root.get("salary"), 10000));
Артём Уровень 111
14 августа 2023
чтобы убедиться в задаче, что класс Employee не изменялся, и чтобы валидатор принял решение, нужно изменить класс Employee, добавив над айдишником @GeneratedValue(strategy = GenerationType.IDENTITY)
Oleg Уровень 41
13 января 2023
Я вижу пользу Criteria API лишь в том, что синтаксис запроса будет проверен при компиляции, а при использовании sql (в nativeQuery например) запрос только при выполнении проверится, но запросы надо проверять всегда прежде, чем коммитить изменения. А так огромный минус критерии в том, что нужно дополнительно к sql изучать её синтаксис, а от изучения sql все равно не уйти. На реальных проектах почти не встречал критерию
Justinian Уровень 41 Master
9 февраля 2023
а я на большинстве встречал, хотя безусловно, проекты очень сильно разные, и на них встретить можно что угодно, или не встретить. Критерия АПИ немного морочная попервах, но это удобный инструмент с гибкой настройкой и многими возможностями. Но конечно, как и много что, одну и ту же задачу можно выполнить разными способами, это просто один из многих инструментов. И на собесах не часто, но спрашивают и попастся может, помимо того что это базовая тема в контексте Персистенса, для общего развития познакомится с таким подходом / паттерном тоже полезно, в той или иной мере много где можно встретить.
Sergey Drogunov Уровень 117 Expert
22 декабря 2022
Удаление через Criteria API Хотели сделать автоматическое создание таблицы, но недоделали
Руслан Уровень 108 Expert
18 декабря 2022
В задача пункт "Не изменяй класс Employee" не выполнимый, потому что при загрузке задачи класс не правильный. Корректный класс можно получить только открыв правильное решение.
Sergey Drogunov Уровень 117 Expert
22 декабря 2022
У меня нормально поехала, после ручного создания таблицы Хахахах понял прикол, тоже самое)))
Джама Уровень 108 Expert
13 января 2023
Ту же ошибку выдало, проблема решилась копипастом Employee, который я даже не трогала
Константин Уровень 100 Expert
22 января 2023
Что бы валидатор принял решение, нужно в класс Employee в поле id добавить аннотацию @GeneratedValue(strategy = GenerationType.IDENTITY). Тогда будет все ок.
Денис Уровень 108 Expert
1 декабря 2022
Во-первых, запросы на HQL не такие уж и короткие, если тебе нужно передавать в них параметры. Сравни: далее пример в котором hql 190 символов. Criteria API 254 символов. Конечно, я согласен, что в hql запросы не короткие, я бы сказал, что они огромные. Я не понимаю, почему это преподносится как польза в Criteria API. Причем в Criteria API намного больше символом и читать совершенно не комфортно. Во-вторых, очень часто бывает ситуация, когда запрос нужно сконструировать динамически. А разве hql не позволяет сконструировать запросы динамически? Но в любом случае, спасибо что познакомили с Criteria API. Если в вакансии будет оное указанно, то пожалуй откажусь от работы в такой "компании мечты".
Sergey Drogunov Уровень 117 Expert
20 декабря 2022
С первым согласен, А чем второе преимущество не понравилось?? Динамическое построение это же классно или я что то не понял в комментарии?
Эльдар Уровень 108 Expert
10 октября 2023
)))откажусь от работы в компании мечты=))) улыбнуло