В этом руководстве рассмотрим, как использовать запросы 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 возвращает набор всех элементов. Посмотрим, как это происходит:
- Объект SessionFactory создает экземпляр Session.
- Session возвращает экземпляр CriteriaBuilder, используя метод getCriteriaBuilder().
- CriteriaBuilder использует метод createQuery(), что создает объект CriteriaQuery(), который далее возвращает экземпляр Query.
- В конце вызываем метод 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 и здесь.