Перейти к содержанию

JPA и Hibernate – запросы Criteria, JPQL и HQL

В этом руководстве рассмотрим, как использовать запросы JPA и Hibernate, а также разницу между запросами Criteria, JPQL и HQL. Запросы Criteria позволяют пользователю писать запросы без использования необработанного SQL. Наряду с запросами Criteria рассмотрим написание именованных запросов Hibernate и способы использования аннотации @Query в Spring Data JPA.

Прежде чем углубимся в это, необходимо отметить, что Hibernate Criteria API устарел, начиная с Hibernate 5.2. Поэтому в примерах будем использовать JPA Criteria API, поскольку это новый и предпочтительный инструмент для написания запросов Criteria. Итак, с этого момента будем называть его просто Criteria API.

Запросы Criteria

Criteria API помогает создавать объект запроса Criteria, применяя к нему различные фильтры и логические условия. Это альтернативный способ манипулирования объектами и возврата нужных данных из таблицы СУБД.

Метод createCriteria() из Hibernate Session возвращает экземпляр персистентного объекта для выполнения запроса Criteria в приложении. Проще говоря, Criteria API создает запрос критериев, который применяет различные фильтры и логические условия.

Зависимости Maven

Возьмем последнюю версию эталонной зависимости JPA, которая реализует JPA в Hibernate, и добавим ее в pom.xml:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.3.2.Final</version>
</dependency>

Использование запросов Criteria и выражений

В соответствии с условиями пользователя CriteriaBuilder управляет результатами запроса. Он использует метод where() из CriteriaQuery, который предоставляет выражения CriteriaBuilder.

Посмотрим на объект, который будем использовать в этой статье:

public class Employee {

    private Integer id;
    private String name;
    private Long salary;

   // стандартные геттеры и сеттеры
}

Рассмотрим простой запрос Criteria, который извлечет все строки Employee из базы данных:

Session session = HibernateUtil.getHibernateSession();
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Employee> cr = cb.createQuery(Employee.class);
Root<Employee> root = cr.from(Employee.class);
cr.select(root);

Query<Employee> query = session.createQuery(cr);
List<Employee> results = query.getResultList();
session.close();
return results;

Приведенный выше запрос Criteria возвращает набор всех элементов. Посмотрим, как это происходит:

  1. Объект SessionFactory создает экземпляр Session.
  2. Session возвращает экземпляр CriteriaBuilder, используя метод getCriteriaBuilder().
  3. CriteriaBuilder использует метод createQuery(), что создает объект CriteriaQuery(), который далее возвращает экземпляр Query.
  4. В конце вызываем метод getResult() для получения объекта запроса, содержащего результаты.

Посмотрим на другое выражение из CriteriaQuery:

cr.select(root).where(cb.gt(root.get("salary"), 50000));

В качестве результата приведенный выше запрос возвращает сотрудников с окладом более 50000.

JPQL

JPQL расшифровывается как Java Persistence Query Language. Spring Data предоставляет несколько способов создания и выполнения запроса, и JPQL – один из них. Он определяет запросы с использованием аннотации @Query в Spring для выполнения как JPQL, так и нативных запросов SQL. В определении запроса по умолчанию используется JPQL.

Мы используем аннотацию @Query для определения SQL-запроса в Spring. Любой запрос, определенный аннотацией @Query, имеет более высокий приоритет по сравнению с именованными запросами, которые аннотированы @NamedQuery.

Использование запросов JPQL

Построим динамический запрос, используя JPQL:

@Query(value = "SELECT e FROM Employee e")
List<Employee> findAllEmployees(Sort sort);

С запросами JPQL, имеющими параметры-аргументы, Spring Data передает аргументы метода в запрос в том же порядке, что и объявление метода. Рассмотрим пару примеров, в которых аргументы метода передаются в запрос:

@Query("SELECT e FROM Employee e WHERE e.salary = ?1")
Employee findAllEmployeesWithSalary(Long salary);
@Query("SELECT e FROM Employee e WHERE e.name = ?1 and e.salary = ?2")
Employee findEmployeeByNameAndSalary(String name, Long salary);

Для последнего вышеприведенного запроса аргумент метода name передается в качестве параметра запроса относительно индекса 1, а аргумент salary передается в качестве параметра запроса индекса 2.

Использование нативных запросов JPQL

Можно выполнять эти SQL-запросы непосредственно в базах данных, используя нативные запросы, которые ссылаются на реальные базы данных и объекты в таблице. Необходимо установить значение атрибута nativeQuery в true для определения нативного SQL-запроса. Нативный SQL-запрос будет определен в атрибуте value аннотации.

Посмотрим на нативный запрос, который показывает индексированный параметр, который нужно передать в качестве аргумента для запроса:

@Query(
  value = "SELECT * FROM Employee e WHERE e.salary = ?1",
  nativeQuery = true)
Employee findEmployeeBySalaryNative(Long salary);

Использование именованных параметров упрощает чтение запроса и делает его менее подверженным ошибкам в случае рефакторинга. Посмотрим на иллюстрацию простого именованного запроса в JPQL и нативном формате:

@Query("SELECT e FROM Employee e WHERE e.name = :name and e.salary = :salary")
Employee findEmployeeByNameAndSalaryNamedParameters(
  @Param("name") String name,
  @Param("salary") Long salary);

Параметры метода передаются в запрос с использованием именованных параметров. Можно определить именованные запросы, используя аннотацию @Param внутри объявления метода репозитория. В результате аннотация @Param должна иметь строковое значение, совпадающее с соответствующим именем запроса JPQL или SQL.

@Query(value = "SELECT * FROM Employee e WHERE e.name = :name and e.salary = :salary", 
  nativeQuery = true) 
Employee findUserByNameAndSalaryNamedParamsNative( 
  @Param("name") String name, 
  @Param("salary") Long salary);

HQL

HQL расшифровывается как язык запросов Hibernate. Это объектно-ориентированный язык, похожий на SQL, который можно использовать для запросов к базе данных. Однако главный недостаток – плохая читаемость кода. Можно определить запросы как именованные запросы, чтобы поместить их в фактический код, который обращается к базе данных.

Использование именованного запроса Hibernate

Именованный запрос определяет запрос с предопределенной неизменной строкой запроса. Эти запросы безотказны, поскольку они проверяются во время создания фабрики сессий. Определим именованный запрос, используя аннотацию org.hibernate.annotations.NamedQuery:

@NamedQuery(name = "Employee_FindByEmployeeId",
 query = "from Employee where id = :id")

Каждая аннотация @NamedQuery присоединяется только к одному классу сущностей. Можно использовать аннотацию @NamedQueries, чтобы сгруппировать более одного именованного запроса для сущности:

@NamedQueries({
    @NamedQuery(name = "Employee_findByEmployeeId", 
      query = "from Employee where id = :id"),
    @NamedQuery(name = "Employee_findAllByEmployeeSalary", 
      query = "from Employee where salary = :salary")
})

Хранимые процедуры и выражения

В заключение, можно использовать аннотацию @NamedNativeQuery для хранения процедур и функций:

@NamedNativeQuery(
  name = "Employee_FindByEmployeeId", 
  query = "select * from employee emp where id=:id", 
  resultClass = Employee.class)

Преимущества запросов Criteria по сравнению с запросами HQL и JPQL

Главное преимущество запросов Criteria по сравнению с HQL – приятный, чистый, объектно-ориентированный API. В результате можно обнаружить ошибки в Criteria API во время компиляции.

Кроме того, запросы JPQL и запросы Criteria имеют одинаковую производительность и эффективность.

Запросы Criteria более гибкие и обеспечивают лучшую поддержку написания динамических запросов по сравнению с HQL и JPQL.

Но HQL и JPQL обеспечивают встроенную поддержку запросов, которая невозможна с запросами Criteria. Это один из недостатков запросов Criteria.

Можно легко писать сложные join-запросы, используя нативные запросы JPQL, тогда как ими сложно управлять, применяя то же самое с Criteria API.

6. Заключение

В этой статье рассмотрели основы запросов Criteria, JPQL и HQL в Hibernate и JPA. Кроме того, узнали, как использовать эти запросы, а также преимущества и недостатки каждого подхода.

Полные примеры кода, используемые в этой статье, можно найти на GitHub и здесь.

Оригинал