Рубрики
Без рубрики

Весенние данные JPA @Query

Узнайте, как использовать аннотацию @Query в Spring Data JPA для определения пользовательских запросов с использованием JPQL и собственного SQL.

Автор оригинала: baeldung.

1. Обзор

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

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

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

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

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

Весенние данные JPA @Изменение аннотации

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

Чтобы определить SQL для выполнения для метода хранилища данных Spring, мы можем аннотировать метод с помощью @Query аннотации — его атрибут value содержит JPQL или SQL для выполнения.

Аннотация @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 с аннотацией @Query . Он будет переведен в предложение ORDER BY , которое передается в базу данных.

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

Для методов, которые мы получаем из коробки, таких как findAll(Sort) или те, которые генерируются путем анализа сигнатур методов, мы можем использовать только свойства объекта для определения нашей сортировки :

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

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

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

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

org.springframework.data.mapping.Исключение PropertyReferenceException: Для типа User не найдена ДЛИНА свойства(имя)!

3.2. JPQL

Когда мы используем JPQL для определения запроса, то данные Spring могут обрабатывать сортировку без каких — либо проблем – все, что нам нужно сделать, это добавить параметр метода типа Sort :

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

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

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

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

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

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

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

Sort.by("LENGTH(name)");

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

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

3.3. Родной язык

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

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

org.springframework.data.jpa.repository.запрос.Недопустимое исключение JpaQueryMethod: Невозможно использовать собственные запросы с динамической сортировкой и/или разбиением на страницы

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

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

4. Разбиение на страницы

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

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

4.1. JPQL

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

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

Мы можем пройти PageRequest параметр для получения страницы данных.

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

4.2. Родной язык

Мы можем включить разбиение на страницы для собственных запросов, объявив дополнительный атрибут 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);

В приведенном выше примере мы добавляем “\n– #pageable\n” в качестве заполнителя для параметра разбиения на страницы. Это говорит Spring Data JPA о том, как проанализировать запрос и ввести параметр с возможностью просмотра страниц. Это решение работает для базы данных H2 .

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

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

Существует два возможных способа передачи параметров метода в наш запрос: индексированные и именованные параметры.

В этом разделе мы рассмотрим индексированные параметры.

5.1. JPQL

Для индексированных параметров в JPQL данные Spring будут передавать параметры метода в запрос в том же порядке, в котором они отображаются в объявлении метода :

@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 method будет назначен параметру запроса с индексом 1, и параметр name method будет назначен параметру запроса с индексом 2 .

5.2. Родной язык

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

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

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

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

Мы также можем передать параметры метода в запрос, используя именованные параметры. Мы определяем их с помощью аннотации @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

Мы можем использовать аннотацию @ Query для изменения состояния базы данных, также добавив метод @ Modifying annotation в репозиторий.

8.1. JPQL

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

@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 , так и использовать собственный запрос, поскольку ВСТАВКА не является частью интерфейса JPA :

@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. Пример динамического запроса

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

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

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

В этом случае мы не можем просто использовать аннотацию @Query , так как мы не можем предоставить статическую инструкцию SQL.

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

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

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

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

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();
    }
}

Как показано выше, мы использовали API критериев JPA чтобы построить наш динамический запрос.

Кроме того, нам нужно обязательно включить постфикс Impl в имя класса. 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 .

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

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