1. Обзор
В этом руководстве мы обсудим различные типы запросов JPA . Более того, мы сосредоточимся на сравнении различий между ними и подробном рассмотрении плюсов и минусов каждого из них.
2. Настройка
Во-первых, давайте определим класс UserEntity
, который мы будем использовать для всех примеров в этой статье:
@Table(name = "users")
@Entity
public class UserEntity {
@Id
private Long id;
private String name;
//Standard constructor, getters and setters.
}
Существует три основных типа запросов JPA:
Query
, написанный на синтаксисе Java Persistence Query Language (JPQL).NativeQuery
, написанный на простом синтаксисе SQL .Criteria API Query
, построенный программно с помощью различных методов
Давайте исследуем их.
3. Запрос
Синтаксис Query
аналогичен SQL и обычно используется для выполнения операций CRUD:
public UserEntity getUserByIdWithPlainQuery(Long id) {
Query jpqlQuery = getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id");
jpqlQuery.setParameter("id", id);
return (UserEntity) jpqlQuery.getSingleResult();
}
Этот запрос
извлекает соответствующую запись из таблицы пользователей
, а также сопоставляет ее с объектом UserEntity
.
Существует два дополнительных подтипа запроса
:
типизированный запрос
Именованный запрос
Давайте посмотрим на них в действии.
3.1. типизированный запрос
Нам нужно обратить внимание на оператор return
в нашем предыдущем примере. JPA не может определить, каким будет тип результата Query , и в результате нам приходится выполнять приведение типов.
Но JPA предоставляет специальный подтип Query , известный как
TypedQuery.
Это всегда предпочтительнее, если мы заранее знаем тип результата нашего запроса .
Кроме того, это делает наш код более надежным и простым для тестирования.
Давайте посмотрим на альтернативу TypedQuery
по сравнению с нашим первым примером:
public UserEntity getUserByIdWithTypedQuery(Long id) {
TypedQuery<UserEntity> typedQuery
= getEntityManager().createQuery("SELECT u FROM UserEntity u WHERE u.id=:id", UserEntity.class);
typedQuery.setParameter("id", id);
return typedQuery.getSingleResult();
}
Таким образом, мы получаем более сильную печать бесплатно, избегая возможных исключений в будущем.
3.2. Именованный запрос
Хотя мы можем динамически определять Query
для конкретных методов, они могут в конечном итоге превратиться в кодовую базу, которую трудно поддерживать. Что, если бы мы могли хранить общие запросы в одном централизованном, удобном для чтения месте?
JPA также помог нам в этом с помощью другого подтипа Query
, известного как NamedQuery
.
Мы можем определить NamedQueries
в orm.xml
или файле свойств.
Кроме того, мы можем определить NamedQuery
в самом классе Entity
, предоставляя централизованный, быстрый и простой способ чтения и поиска запросов, связанных с Entity .
Все NamedQueries
должны иметь уникальное имя.
Давайте посмотрим, как мы можем добавить NamedQuery
в наш класс UserEntity
:
@Table(name = "users")
@Entity
@NamedQuery(name = "UserEntity.findByUserId", query = "SELECT u FROM UserEntity u WHERE u.id=:userId")
public class UserEntity {
@Id
private Long id;
private String name;
//Standard constructor, getters and setters.
}
Аннотацию @NamedQuery
необходимо сгруппировать внутри аннотации @NamedQueries
, если мы используем Java до версии 8. Начиная с Java 8, мы можем просто повторить аннотацию @NamedQuery
в нашем классе Entity .
Использовать NamedQuery
очень просто:
public UserEntity getUserByIdWithNamedQuery(Long id) {
Query namedQuery = getEntityManager().createNamedQuery("UserEntity.findByUserId");
namedQuery.setParameter("userId", id);
return (UserEntity) namedQuery.getSingleResult();
}
4. Нативный запрос
NativeQuery — это
просто SQL-запрос. Это позволяет нам раскрыть всю мощь нашей базы данных, поскольку мы можем использовать проприетарные функции, недоступные в синтаксисе с ограничениями JPQL.
Это дорого обходится. Мы теряем переносимость базы данных нашего приложения с NativeQuery,
потому что наш поставщик JPA больше не может абстрагировать конкретные детали от реализации базы данных или поставщика.
Давайте посмотрим, как использовать NativeQuery
, который дает те же результаты, что и в наших предыдущих примерах:
public UserEntity getUserByIdWithNativeQuery(Long id) {
Query nativeQuery
= getEntityManager().createNativeQuery("SELECT * FROM users WHERE id=:userId", UserEntity.class);
nativeQuery.setParameter("userId", id);
return (UserEntity) nativeQuery.getSingleResult();
}
Мы всегда должны учитывать, является ли NativeQuery
единственным вариантом. В большинстве случаев хороший JPQL- запрос
может удовлетворить наши потребности и, что наиболее важно, поддерживать уровень абстракции от фактической реализации базы данных.
Использование NativeQuery
не обязательно означает привязку к одному конкретному поставщику базы данных. В конце концов, если в наших запросах не используются проприетарные команды SQL, а используется только стандартный синтаксис SQL, смена провайдера не должна быть проблемой.
5. Query, NamedQuery
и NativeQuery
Итак, мы узнали о Query
, NamedQuery
и NativeQuery
.
Теперь давайте быстро вернемся к ним и суммируем их плюсы и минусы.
5.1. Запрос
Мы можем создать запрос, используя entityManager.createQuery(queryString)
.
Далее давайте рассмотрим плюсы и минусы Query
:
Плюсы:
- Когда мы создаем запрос с помощью
EntityManager
, мы можем создавать динамические строки запроса. - Запросы написаны на JPQL, поэтому они переносимы.
Минусы:
- Для динамического запроса он может быть скомпилирован в собственную инструкцию SQL несколько раз в зависимости от кеша плана запроса .
- Запросы могут быть разбросаны по различным классам Java и смешаны с кодом Java. Поэтому его может быть сложно поддерживать, если проект содержит много запросов.
5.2. Именованный запрос
После определения NamedQuery
мы можем сослаться на него с помощью EntityManager
:
entityManager.createNamedQuery(queryName);
Теперь давайте посмотрим на преимущества и недостатки NamedQueries
:
Плюсы:
NamedQueries
компилируются и проверяются при загрузке модуля сохраняемости. То есть они компилируются только один раз.- Мы можем централизовать
NamedQueries
, чтобы упростить их обслуживание — например, вorm.xml
, в файлах свойств или в классах@Entity.
Минусы:
NamedQueries
всегда статичныНа NamedQueries
можно ссылаться в репозиториях Spring Data JPA. Однако динамическая сортировка не поддерживается.
5.3. Нативный запрос
Мы можем создать NativeQuery
с помощью EntityManager
:
entityManager.createNativeQuery(sqlStmt);
В зависимости от сопоставления результатов мы также можем передать методу второй параметр, например класс Entity
, как мы видели в предыдущем примере.
У NativeQueries тоже
есть плюсы и минусы. Давайте быстро посмотрим на них:
Плюсы:
- По мере того как наши запросы становятся сложными, иногда операторы SQL, сгенерированные JPA, оказываются не самыми оптимизированными. В этом случае мы можем использовать
NativeQueries
, чтобы сделать запросы более эффективными. NativeQueries
позволяет нам использовать функции, специфичные для поставщиков баз данных. Иногда эти функции могут повысить производительность наших запросов.
Минусы:
- Специфичные для поставщика функции могут принести удобство и повысить производительность, но мы платим за это преимущество, теряя переносимость с одной базы данных на другую.
6. API-запрос критериев
Запросы Criteria
API — это программно созданные, типобезопасные запросы, несколько похожие на запросы JPQL по синтаксису:
public UserEntity getUserByIdWithCriteriaQuery(Long id) {
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<UserEntity> criteriaQuery = criteriaBuilder.createQuery(UserEntity.class);
Root<UserEntity> userRoot = criteriaQuery.from(UserEntity.class);
UserEntity queryResult = getEntityManager().createQuery(criteriaQuery.select(userRoot)
.where(criteriaBuilder.equal(userRoot.get("id"), id)))
.getSingleResult();
return queryResult;
}
Использование запросов Criteria
API из первых рук может быть пугающим , но они могут быть отличным выбором, когда нам нужно добавить элементы динамического запроса или в сочетании с метамоделью
JPA . ``
7. Заключение
В этой быстрой статье мы узнали, что такое запросы JPA, а также их использование.
Запросы JPA — отличный способ абстрагировать нашу бизнес-логику от нашего уровня доступа к данным, поскольку мы можем полагаться на синтаксис JPQL и позволить нашему поставщику JPA по выбору обрабатывать перевод запросов .
Весь код, представленный в этой статье, доступен на GitHub .