Spring Data JPA @Query

przegląd

Spring Data zapewnia wiele sposobów definiowania zapytania, które możemy wykonać. Jedną z nich jest adnotacja @ Query.

w tym samouczku zademonstrujemy, jak używać adnotacji @Query w Spring Data JPA do wykonywania zarówno zapytań JPQL, jak i natywnych zapytań SQL.

pokażemy również, jak zbudować dynamiczne zapytanie, gdy adnotacja @Query nie wystarczy.

Czytaj dalej:

pochodne metody zapytań w repozytoriach Spring Data JPA

eksplorują mechanizm wyprowadzania zapytań w Spring Data JPA.
Czytaj więcej →

Adnotacja Spring Data JPA @Modifying

twórz zapytania DML i DDL w Spring Data JPA, łącząc adnotacje @Query i @ Modifying
Czytaj więcej →

Select Query

aby zdefiniować SQL do wykonania dla metody Spring Data repository, możemy przypisać metodę adnotacją @Query — jej atrybut value zawiera JPQL lub SQL do wykonania.

adnotacja @Query ma pierwszeństwo przed zapytaniami nazwanymi, które są adnotowane za pomocą @NamedQuery lub zdefiniowane w orm.plik xml.

dobrym podejściem jest umieszczenie definicji zapytania tuż nad metodą wewnątrz repozytorium, a nie wewnątrz naszego modelu domeny jako nazwane zapytania. Repozytorium jest odpowiedzialne za trwałość, więc jest to lepsze miejsce do przechowywania tych definicji.

2.1. JPQL

domyślnie definicja zapytania używa JPQL.

przyjrzyjmy się prostej metodzie repozytorium, która zwraca aktywne jednostki użytkownika z bazy danych:

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

2.2. Natywny

możemy użyć również natywnego SQL do zdefiniowania naszego zapytania. Wszystko, co musimy zrobić, to ustawić wartość atrybutu nativeQuery na true i zdefiniować natywne zapytanie SQL w atrybucie value w adnotacji:

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

Zdefiniuj kolejność w zapytaniu

możemy przekazać dodatkowy parametr typu Sort do deklaracji Spring Data method, która ma adnotację @Query. Zostanie to przetłumaczone na klauzulę ORDER BY, która zostanie przekazana do bazy danych.

3.1. Sortowanie dla dostarczonych i pochodnych metod JPA

dla metod, które otrzymujemy z pudełka, takich jak findAll (Sort) lub tych, które są generowane przez parsowanie podpisów metod, możemy użyć właściwości obiektu tylko do zdefiniowania naszego sortowania:

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

teraz wyobraź sobie, że chcemy posortować według długości właściwości name:

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

kiedy wykonamy powyższy kod, otrzymamy wyjątek:

org.springframework.data.mapowanie.Propertyreferencexception: nie znaleziono właściwości długość(nazwa) dla typu User!

3.2. JPQL

gdy używamy JPQL do definicji zapytania, To Spring Data bez problemu poradzi sobie z sortowaniem — wystarczy dodać parametr metody typu Sort:

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

możemy wywołać tę metodę i przekazać parametr Sort, który uporządkuje wynik według właściwości name obiektu User:

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

a ponieważ użyliśmy adnotacji @ Query, możemy użyć tej samej metody, aby uzyskać posortowaną listę Użytkowników według długości ich nazw:

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

ważne jest, że używamy JpaSort.unsafe() do utworzenia instancji obiektu sortowania.

kiedy używamy:

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

wtedy otrzymamy dokładnie ten sam wyjątek, jaki widzieliśmy powyżej dla metody findAll ().

kiedy Spring Data odkryje niebezpieczną kolejność sortowania dla metody używającej adnotacji @Query, wtedy po prostu doda klauzulę sort do zapytania — pomija sprawdzanie, czy właściwość do sortowania należy do modelu domeny.

3.3. Natywne

gdy adnotacja @Query używa natywnego SQL, nie można zdefiniować sortowania.

jeśli to zrobimy, otrzymamy wyjątek:

org.springframework.data.jpa.repozytorium.zapytanie.InvalidJpaQueryMethodException: nie można używać zapytań natywnych z dynamicznym sortowaniem i/lub paginacją

ponieważ wyjątek mówi, sortowanie nie jest obsługiwane dla zapytań natywnych. Komunikat o błędzie daje nam wskazówkę, że paginacja również spowoduje wyjątek.

istnieje jednak obejście, które umożliwia stronicowanie i omówimy to w następnej sekcji.

paginacja

paginacja pozwala nam zwrócić tylko podzbiór całego wyniku w stronę. Jest to przydatne na przykład podczas nawigacji po kilku stronach danych na stronie internetowej.

Kolejną zaletą paginacji jest zminimalizowanie ilości danych przesyłanych z serwera do klienta. Wysyłając mniejsze kawałki danych, możemy ogólnie zauważyć poprawę wydajności.

4.1. JPQL

używanie stronicowania w definicji zapytania JPQL jest proste:

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

możemy przekazać parametr PageRequest, aby uzyskać stronę z danymi.

paginacja jest również obsługiwana dla zapytań natywnych, ale wymaga trochę dodatkowej pracy.

4.2. Natywne

możemy włączyć stronicowanie dla zapytań natywnych deklarując dodatkowy atrybut countQuery.

to definiuje SQL do wykonania, aby policzyć liczbę wierszy w całym wyniku:

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

4.3. Spring Data JPA w wersjach wcześniejszych niż 2.0.4

powyższe rozwiązanie dla zapytań natywnych działa dobrze dla Spring Data JPA w wersjach 2.0.4 i późniejszych.

przed tą wersją, gdy spróbujemy wykonać takie zapytanie, otrzymamy ten sam wyjątek, który opisaliśmy w poprzedniej sekcji sortowania.

możemy to przezwyciężyć dodając dodatkowy parametr paginacji wewnątrz naszego zapytania:

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

w powyższym przykładzie dodajemy” \n– #pageable\n ” jako symbol zastępczy dla parametru paginacji. To mówi Spring Data JPA, jak przeanalizować zapytanie i wstrzyknąć parametr pageable. To rozwiązanie działa dla bazy danych H2.

omówiliśmy, jak tworzyć proste zapytania select za pomocą JPQL i natywnego SQL. Następnie pokażemy jak zdefiniować dodatkowe parametry.

indeksowane parametry zapytania

istnieją dwa możliwe sposoby przekazywania parametrów metody do naszego zapytania: indeksowane i nazwane parametry.

w tej sekcji omówimy indeksowane parametry.

5.1. JPQL

dla indeksowanych parametrów w JPQL, Spring Data przekaże parametry metody do zapytania w tej samej kolejności, w jakiej występują w deklaracji metody:

@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);

w przypadku powyższych zapytań parametr metody status zostanie przypisany do parametru zapytania o indeksie 1, a parametr metody name zostanie przypisany do parametru zapytania o indeksie 2.

5.2. Natywne

indeksowane parametry dla zapytań natywnych działają dokładnie tak samo jak dla JPQL:

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

w następnej sekcji pokażemy inne podejście: przekazywanie parametrów przez nazwę.

nazwane parametry

możemy również przekazać parametry metody do zapytania za pomocą nazwanych parametrów. Definiujemy je za pomocą adnotacji @ Param wewnątrz deklaracji metody repozytorium.

każdy parametr z adnotacją @ Param musi mieć ciąg wartości pasujący do odpowiedniej nazwy parametru zapytania JPQL lub SQL. Zapytanie z nazwanymi parametrami jest łatwiejsze do odczytania i mniej podatne na błędy w przypadku, gdy zapytanie musi zostać refakturowane.

6.1. JPQL

jak wspomniano powyżej, używamy adnotacji @ Param w deklaracji metody, aby dopasować parametry zdefiniowane przez nazwę w JPQL z parametrami z deklaracji metody:

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

zauważ, że w powyższym przykładzie zdefiniowaliśmy nasze zapytanie SQL i parametry metody tak, aby miały te same nazwy, ale nie są wymagane, o ile łańcuchy wartości są takie same:

@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. Natywna

dla natywnej definicji zapytania nie ma różnicy w tym, jak przekazujemy parametr poprzez nazwę do zapytania w porównaniu do JPQL-używamy adnotacji @ 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. Parametr Collection

rozważmy przypadek, gdy klauzula where naszego zapytania JPQL lub SQL zawiera słowo kluczowe IN (or NOT IN) :

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

w tym przypadku możemy zdefiniować metodę zapytania, która jako parametr przyjmuje zbiór:

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

ponieważ parametr jest kolekcją, może być używany z listą, HashSet, itp.

następnie pokażemy, jak modyfikować dane za pomocą adnotacji @ Modifying.

Aktualizuj zapytania za pomocą @Modifying

możemy użyć adnotacji @Query, aby zmodyfikować stan bazy danych, dodając również adnotację @Modifying do metody repozytorium.

8.1. JPQL

metoda repozytorium, która modyfikuje dane, ma dwie różnice w porównaniu z zapytaniem select-ma adnotację @ Modifying i, oczywiście, zapytanie JPQL używa update zamiast select:

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

wartość zwracana określa, ile wierszy zaktualizowano wykonanie zapytania. Zarówno indeksowane, jak i nazwane parametry mogą być używane w zapytaniach aktualizacji.

8.2. Natywny

możemy modyfikować stan bazy danych również za pomocą natywnego zapytania. Musimy tylko dodać adnotację @ Modifying:

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

8.3. Inserts

aby wykonać operację insert, musimy zarówno zastosować @ Modifying, jak i użyć natywnego zapytania, ponieważ INSERT nie jest częścią interfejsu 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);

zapytanie dynamiczne

często napotykamy na potrzebę budowania instrukcji SQL w oparciu o warunki lub zbiory danych, których wartości są znane tylko w czasie wykonywania. W takich przypadkach nie możemy po prostu użyć zapytania statycznego.

9.1. Przykład zapytania dynamicznego

na przykład, wyobraźmy sobie sytuację, w której musimy wybrać wszystkich użytkowników, których email jest taki sam z zestawu zdefiniowanego w runtime-email1, email2,…, emailn:

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

ponieważ zbiór jest konstruowany dynamicznie, nie możemy wiedzieć w czasie kompilacji, ile podobnych klauzul dodać.

w tym przypadku nie możemy po prostu użyć adnotacji @Query, ponieważ nie możemy dostarczyć statycznej instrukcji SQL.

zamiast tego, implementując niestandardowe repozytorium złożone, możemy rozszerzyć podstawową funkcjonalność JpaRepository i dostarczyć własną logikę budowania dynamicznego zapytania. Zobaczmy, jak to zrobić.

9.2. Niestandardowe repozytoria i API kryteriów JPA

na szczęście dla nas, Spring zapewnia sposób na rozszerzenie podstawowego repozytorium poprzez użycie niestandardowych interfejsów fragment. Następnie możemy połączyć je ze sobą, aby utworzyć złożone repozytorium.

zaczniemy od stworzenia niestandardowego interfejsu fragmentu:

public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails);}

a potem go wdrożymy:

public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate))); return entityManager.createQuery(query) .getResultList(); }}

jak pokazano powyżej, wykorzystaliśmy interfejs API kryteriów JPA, aby zbudować nasze dynamiczne zapytanie.

ponadto musimy upewnić się, że w nazwie klasy znajduje się postfix Impl. Spring przeszukuje implementację UserRepositoryCustom jako userrepositorycustomimpl. Ponieważ fragmenty nie są repozytoriami samymi w sobie, Spring opiera się na tym mechanizmie, aby znaleźć implementację fragmentu.

9.3. Rozszerzenie istniejącego repozytorium

zauważ, że wszystkie metody zapytań od sekcji 2 do sekcji 7 znajdują się w UserRepository.

więc teraz integrujemy nasz fragment rozszerzając nowy interfejs w UserRepository:

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

9.4. Korzystając z repozytorium

i wreszcie możemy wywołać naszą dynamiczną metodę zapytań:

Set<String> emails = new HashSet<>();// filling the set with any number of itemsuserRepository.findUserByEmails(emails);

z powodzeniem stworzyliśmy repozytorium złożone i nazwaliśmy naszą niestandardową metodę.

podsumowanie

w tym artykule omówiliśmy kilka sposobów definiowania zapytań w metodach Spring Data JPA repozytorium przy użyciu adnotacji @Query.

dowiedzieliśmy się również, jak zaimplementować niestandardowe repozytorium i utworzyć dynamiczne zapytanie.

jak zawsze, kompletne przykłady kodu użyte w tym artykule są dostępne na GitHub.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.