Перейти к основному содержимому

Типы запросов JPA

· 6 мин. чтения

Задача: Медиана двух отсортированных массивов

Даны два отсортированных массива размерами n и m. Найдите медиану слияния этих двух массивов.
Временная сложность решения должна быть O(log(m + n)) ...

ANDROMEDA

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 .