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();

2. 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();

3. 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();

4. Користь від Criteria API

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

По-перше, запити на HQL не такі короткі, якщо тобі потрібно передавати до них параметри. Порівняй:

Рахуємо кількість співробітників із зарплатою меншою за 10 тисяч
HQL
String hqlQuery = "from Employee where salary < :sal";
Query<Employee> query = session. createQuery(hqlQuery);
query.setParameter("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();

По-друге, часто буває ситуація, коли запит потрібно сконструювати динамічно. Наприклад, у тебе на вебсторінці є фільтрація співробітників, квартир та чого завгодно. І якщо користувачеві якийсь параметр не важливий, він його просто не вказує. Відповідно, на сервер замість нього передається 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 можуть виручати тебе. Такі справи.

Докладніше з темою можна ознайомитися в офіційній документації.