Spring Data JPA @Query

Spring Data JPA @Query

1. обзор

Spring Data предоставляет множество способов определить запрос, который мы можем выполнить. Одна из них - аннотация@Query.

В этом руководстве мы продемонстрируемhow to use the @Query annotation in Spring Data JPA to execute both JPQL and native SQL queries.

Кроме того, мы покажем, как построить динамический запрос, когда аннотации@Query недостаточно.

Дальнейшее чтение:

Методы производных запросов в JPA-репозиториях Spring Data

Изучите механизм формирования запросов в Spring Data JPA.

Read more

Spring Data JPA @ Модифицирующая аннотация

Создавайте запросы DML и DDL в Spring Data JPA, комбинируя аннотации @Query и @Modifying.

Read more

2. Выберите запрос

Чтобы определить SQL для выполнения для метода репозитория Spring Data, мы можемannotate the method with the @Query annotation  — its value attribute contains the JPQL or SQL to execute.

Аннотация@Query имеет приоритет над именованными запросами, которые аннотируются@NamedQuery или определены в файлеorm.xml.

Хороший подход - разместить определение запроса чуть выше метода внутри репозитория, а не внутри нашей модели предметной области в виде именованных запросов. Репозиторий отвечает за постоянство, поэтому лучше всего хранить эти определения.

2.1. JPQL

По умолчанию в определении запроса используется JPQL.

Давайте посмотрим на простой метод репозитория, который возвращает активные объектыUser из базы данных:

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection findAllActiveUsers();

2.2. Родной

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

@Query(
  value = "SELECT * FROM USERS u WHERE u.status = 1",
  nativeQuery = true)
Collection findAllActiveUsersNative();

3. Определить порядок в запросе

Мы можем передать дополнительный параметр типаSort в объявление метода Spring Data с аннотацией@Query. Он будет переведен в предложениеORDER BY, которое передается в базу данных.

3.1. Сортировка по предоставленным и производным методам JPA

Для методов, которые мы получаем "из коробки", таких какfindAll(Sort) или тех, которые генерируются путем анализа сигнатур методов,we can only use object properties to define our sort:

userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));

Теперь представьте, что мы хотим отсортировать по длине свойства name:

userRepository.findAll(new Sort("LENGTH(name)"));

Когда мы выполним приведенный выше код, мы получим исключение:

org.springframework.data.mapping.PropertyReferenceException: No property lENGTH(name) found for type User!

3.2. JPQL

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem - все, что нам нужно сделать, это добавить параметр метода типаSort:

@Query(value = "SELECT u FROM User u")
List findAllUsers(Sort sort);

Мы можем вызвать этот метод и передать параметрSort, который упорядочит результат по свойствуname объектаUser:

userRepository.findAllUsers(new Sort("name"));

И поскольку мы использовали аннотацию@Query,we can use the same method to get the sorted list of Users by the length of their names:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

Очень важно использоватьJpaSort.unsafe() для создания экземпляра объектаSort.

Когда мы используем:

new Sort("LENGTH(name)");

тогда мы получим точно такое же исключение, как мы видели выше для методаfindAll().

Когда Spring Data обнаруживает небезопасный порядокSort для метода, использующего аннотацию@Query, он просто добавляет предложение сортировки к запросу - он пропускает проверку того, принадлежит ли свойство для сортировки модели предметной области .

3.3. Родной

Когда в аннотации@Query используется собственный SQL, тогда невозможно определитьSort.

Если мы это сделаем, мы получим исключение:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or pagination

Как указано в исключении, сортировка не поддерживается для собственных запросов. Сообщение об ошибке подсказывает нам, что разбиение на страницы также вызовет исключение.

Однако есть обходной путь, позволяющий разбивать на страницы, и мы рассмотрим его в следующем разделе.

4. пагинация

Разбиение на страницы позволяет нам возвращать только подмножество всего результата вPage. Это полезно, например, при навигации по нескольким страницам данных на веб-странице.

Другое преимущество разбиения на страницы заключается в том, что объем данных, отправляемых с сервера на клиент, сводится к минимуму. Посылая меньшие фрагменты данных, мы обычно видим улучшение производительности.

4.1. JPQL

Использование нумерации страниц в определении запроса JPQL просто:

@Query(value = "SELECT u FROM User u ORDER BY id")
Page findAllUsersWithPagination(Pageable pageable);

Мы можем передать параметрPageRequest, чтобы получить страницу данных. Разбивка на страницы также поддерживается для нативных запросов, но требует немного дополнительной работы.

4.2. Родной

Мы можемenable pagination for native queries by declaring an additional attribute countQuery - это определяет SQL, который нужно выполнить для подсчета количества строк во всем результате:

@Query(
  value = "SELECT * FROM Users ORDER BY id",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

4.3. Версии Spring Data JPA до 2.0.4

Приведенное выше решение для нативных запросов отлично работает для Spring Data JPA версии 2.0.4 и выше.

До этой версии, когда мы пытаемся выполнить такой запрос, мы получали исключение - то же самое, что мы описали в предыдущем разделе о сортировке.

Мы можем преодолеть это, добавив дополнительный параметр для разбивки на страницы внутри нашего запроса:

@Query(
  value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n",
  countQuery = "SELECT count(*) FROM Users",
  nativeQuery = true)
Page findAllUsersWithPagination(Pageable pageable);

”В качестве заполнителя для параметра разбивки на страницы. Это сообщает Spring Data JPA, как анализировать запрос и вводить параметр pageable. Это решение работает для базы данныхH2.

Мы рассмотрели, как создавать простые запросы на выборку с помощью JPQL и собственного SQL. Далее мы покажем, как определить дополнительные параметры.

5. Индексированные параметры запроса

Есть два возможных способа передачи параметров метода в наш запрос. В этом разделе мы рассмотрим индексированные параметры.

5.1. JPQL

Для индексированных параметров в JPQL Spring Data будетpass method parameters to the query in the same order they appear in the method declaration:

@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);

@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);

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

5.2. Родной

Индексированные параметры для собственных запросов работают точно так же, как и для JPQL:

@Query(
  value = "SELECT * FROM Users u WHERE u.status = ?1",
  nativeQuery = true)
User findUserByStatusNative(Integer status);

В следующем разделе мы покажем другой подход - передачу параметров через имя.

6. Именованные параметры

Мы также можемpass method parameters to the query using named parameters.. Мы определяем их, используя аннотацию@Param внутри нашего объявления метода репозитория.

Каждый параметр, помеченный@Param, должен иметь строку значения, соответствующую соответствующему имени параметра запроса JPQL или SQL. Запрос с именованными параметрами проще для чтения и менее подвержен ошибкам в случае необходимости рефакторинга запроса.

6.1. JPQL

Как упоминалось выше, мы используем аннотацию@Param в объявлении метода, чтобы сопоставить параметры, определенные по имени в JPQL, с параметрами из объявления метода:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
  @Param("status") Integer status,
  @Param("name") String name);

Обратите внимание, что в приведенном выше примере мы определили наши параметры SQL-запроса и метода с одинаковыми именами, но это не обязательно, если строки значений одинаковы:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByUserStatusAndUserName(@Param("status") Integer userStatus,
  @Param("name") String userName);

6.2. Родной

Для определения собственного запроса нет разницы, как мы передаем параметр через имя в запрос по сравнению с JPQL - мы используем аннотацию@Param:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name",
  nativeQuery = true)
User findUserByStatusAndNameNamedParamsNative(
  @Param("status") Integer status, @Param("name") String name);

7. Параметр коллекции

Давайте рассмотрим случай, когда предложениеwhere нашего запроса JPQL или SQL содержит ключевое словоIN (илиNOT IN):

SELECT u FROM User u WHERE u.name IN :names

В этом случае мы можем определить метод запроса, который принимает в качестве параметраCollection :

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List findUserByNameList(@Param("names") Collection names);

Поскольку параметр представляет собойCollection, его можно использовать сList, HashSet и т. Д.

Далее мы покажем, как изменять данные с помощью аннотации @Modifying.

8. Обновить запросы с помощью@Modifying

Мы можем использоватьuse the @Query annotation to modify the state of the database by also adding the @Modifying annotation в методе репозитория.

8.1. JPQL

Метод репозитория, который изменяет данные, имеет два отличия по сравнению с запросомselect - он имеет аннотацию@Modifying и, конечно же, запрос JPQL используетupdate вместоselect. s:

@Modifying
@Query("update User u set u.status = :status where u.name = :name")
int updateUserSetStatusForName(@Param("status") Integer status,
  @Param("name") String name);

Возвращаемое значение определяет, сколько строк обновляется при выполнении запроса. И индексированные, и именованные параметры могут использоваться внутри запросов на обновление.

8.2. Родной

Мы можем изменить состояние базы данных также с помощью собственного запроса - нам просто нужно добавить аннотацию@Modifying:

@Modifying
@Query(value = "update Users u set u.status = ? where u.name = ?",
  nativeQuery = true)
int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Вставки

Чтобы выполнить операцию вставки, мы должны одновременно применить@Modifying и использовать собственный запрос, посколькуINSERT is not a part of the JPA interface:

@Modifying
@Query(
  value =
    "insert into Users (name, age, email, status) values (:name, :age, :email, :status)",
  nativeQuery = true)
void insertUser(@Param("name") String name, @Param("age") Integer age,
  @Param("status") Integer status, @Param("email") String email);

9. Динамический запрос

Часто мы сталкиваемся с необходимостью создания операторов SQL на основе условий или наборов данных, значения которых известны только во время выполнения. И в этих случаях мы не можем просто использовать статический запрос.

9.1. Пример динамического запроса

Например, давайте представим ситуацию, когда нам нужно выбрать всех пользователей с адресом электронной почтыLIKE из набора, определенного во время выполнения -email1,email2,…,emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%'
    or  u.email LIKE '%email2%'
    ...
    or  u.email LIKE '%emailn%'

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

В этом случаеwe can’t just use the @Query annotation since we can’t provide a static SQL statement.

Вместо этого, реализовав настраиваемый составной репозиторий, мы можем расширить базовую функциональностьJpaRepository и предоставить нашу собственную логику для построения динамического запроса. Давайте посмотрим, как это сделать.

9.2. Пользовательские репозитории и API критериев JPA

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces. Затем мы можем связать их вместе, чтобы создатьcomposite repository.

Начнем с создания пользовательского интерфейса фрагмента:

public interface UserRepositoryCustom {
    List findUserByEmails(Set emails);
}

А потом мы реализуем это:

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List findUserByEmails(Set emails) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery query = cb.createQuery(User.class);
        Root user = query.from(User.class);

        Path emailPath = user.get("email");

        List predicates = new ArrayList<>();
        for (String email : emails) {
            predicates.add(cb.like(emailPath, email));
        }
        query.select(user)
            .where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
            .getResultList();
    }
}

Как показано выше, мы использовалиJPA Criteria API для создания нашего динамического запроса.

Also, we need to make sure to include the Impl postfix in the class name. Spring будет искать реализациюUserRepositoryCustom какUserRepositoryCustomImpl. Поскольку фрагменты сами по себе не являются репозиториями, Spring использует этот механизм для поиска реализации фрагмента.

9.3. Расширение существующего репозитория

Обратите внимание, что все методы запроса из раздела 2 - раздела 7 находятся вUserRepository.. Итак, теперь мы интегрируем наш фрагмент, расширив новый интерфейс вUserRepository:

public interface UserRepository extends JpaRepository, UserRepositoryCustom {
    //  query methods from section 2 - section 7
}

9.4. Использование репозитория

И, наконец, мы можем вызвать наш метод динамического запроса:

Set emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);

Мы успешно создали составной репозиторий и вызвали наш собственный метод.

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

В этой статье мы рассмотрели несколько способов определения запросов в методах репозитория Spring Data JPA с использованием аннотации@Query.

Также мы узнали, как реализовать собственный репозиторий и создать динамический запрос.

Как всегда, полные примеры кода, используемые в этом руководстве, доступныover on Github.