Spring Data JPA - Zapytania natywne

Kiedy zawiodą nas możliwości oferowane zarówno przez zapytania wbudowane jak i zapytania własne, możemy jeszcze chwycić się ostatniej deski ratunku, jaką są zapytania natywne. Czym są takie zapytania?

Nie będziemy się tutaj dużo rozpisywać, ponieważ idea jest w gruncie rzeczy prosta. Polega na umożliwieniu wprowadzenia do adnotacji @Query, standardowego zapytania SQL (zamiast JPQL) i operowania nim na tabelach (zamiast na encjach). Nie jesteśmy wtedy poddani ograniczeniom JPQL-a, korzystamy ze wszystkich (no prawie) dorodziejstw języka SQL w czystej postaci.

Zapytanie grupujące jako nativeQuery

Wyobraźmy sobie teraz, że musimy napisać zapytanie wymagane do przedstawienia statystyk liczby itemów z podziałem na konkretne kategorie. Aby rozwiązać takie zadanie będziemy musieli napisać zapytanie grupujące (używające klauzuli GROUP BY), które będzie działało pod kontrolą JPARepository:
public interface ItemRepository extends JpaRepository<Item, Long> {

    @Query(value = "select ac.name, count(aci.items_id) from appa_categories_items aci "
            + "inner join appa_categories ac on ac.id=aci.categories_id "
            + "group by aci.categories_id", nativeQuery = true)
    List<Object[]> findAppaItemsNumberByCategory();
}
Podobnie jak to miało miejsce w przypadku zapytań własnych, tak samo w przypadku zapytań natywnych, podczas tworzenia takiego zapytania należy zwrócić uwagę na kilka aspektów:
  • Parametry wykorzystywane w tekście zapytania poprzedzamy dwukropkiem, np.: :lastName
  • Domyślnie parametry metody są przyporządkowane do parametrów z treści zapytania na podstawie kolejności miejsc, więc możemy NIE oznaczać parametrów metody za pomocą adnotacji @Param. Takie podejście bywa jednak problematyczne, w przypadku ewentualnych zmian w metodzie w przyszłości, szczególnie w przypadku refactoringu.
  • Stosujemy adnotację @Param, aby przyporządkować parametry metody do parametrów z treści zapytania na podstawie nazwy przekazanej do adnotacji. Nazwa musi zgadzać się z nazwą użytą w treści zapytania (po dwukropku). Nie jest wtedy wymagana zgodność co do kolejności parametrów.
  • W przypadku gdy chcemy dodać do zapytania opcje stronicowania Pageable lub sortowania Sort, dodajemy te typy na ostatniej pozycji wśród parametrów metody. Tutaj jedna uwaga. W przypadku użycia interfejsu do stronicowania w ramach zapytań natywnych, jesteśmy zobowiązani do podania zapytania zliczającego w ramach adnotacji @Query:
    public interface UserRepository extends JpaRepository<Item, Long> {
    
        @Query(value = "select * from users where lastName = :lastName /*#pageable*/ "
        		+ "order by lastName",
        countQuery = "select count(*) from users where lastName = :lastName",
        nativeQuery = true)
        Page findByLastname(String lastName, Pageable pageable);
    }
    
    
    Na koniec tego wątku musimy wspomnieć o bardzo ważnej rzeczy, która może się wydawać niejasna patrząc na powyższe zapytanie. Dotyczy ona zwrotu /*#pageable*/, który został dodany do zapytania jako workaround, aby takie zapytanie w ogóle zadziałało. Okazuje się bowiem, że Spring Data JPA przed wersją 2.x posiada błąd, który skutkuje tym, że podając w metodzie jako parametr interfejs Pageable nie jesteśmy w stanie takiego zapytania w ogóle uruchomić. Otrzymujemy błąd:
    Caused by: org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: 
    Cannot use native queries with dynamic sorting and/or pagination in method public abstract org.springframework.data.domain.Page 
        com.javappa.startappa.appaimport.repository.UserRepository
        .findByLastname(java.lang.String,org.springframework.data.domain.Pageable)
        at org.springframework.data.jpa.repository.query.NativeJpaQuery.(NativeJpaQuery.java:55)
        at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:72)
        at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromQueryAnnotation(JpaQueryFactory.java:53)               
    
    Jest to o tyle nielogiczne, że w oficjalnej dokumentacji Springa jest napisane, że o ile dynamiczne sortowanie nie jest wspierane, o tyle można użyć natywnych zapytań do paginacji poprzez podanie własnego zapytania COUNT oraz dodanie Pageable jako parametru metody. Okazuje się jednak, że z powodu błędnie zaimplementowanego kodu klasy NativeJpaQuery wymagane jest dodatkowo posiadanie wspomnianego tekstu w treści zapytania:
    public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryString, 
        EvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
       super(method, em, queryString, evaluationContextProvider, parser);
       JpaParameters parameters = method.getParameters();
       boolean hasPagingOrSortingParameter = parameters.hasPageableParameter() 
                        || parameters.hasSortParameter();
       boolean containsPageableOrSortInQueryExpression = queryString.contains("#pageable") 
                        || queryString.contains("#sort");
       if(hasPagingOrSortingParameter && !containsPageableOrSortInQueryExpression) {
           throw new InvalidJpaQueryMethodException("Cannot use native queries with dynamic 
                        sorting and/or pagination in method " + method);
       }
    }
    
    Powyższy błąd został naprawiony przez programistów Springa dopiero w wersji 2.0.4 w marcu 2018 roku.

Zapytanie modyfikujące typu UPDATE lub DELETE

Podobnie jak to miało miejsce w przypadku zapytań własnych (Spring Data JPA - Zapytania własne), natywne zapytania modyfikujące również wymagają dodania adnotacji: @Modifying i @Transactional.
Nasza rekomendacja
Zapytania natywne to super rozwiązanie wielu problemów, ale trzeba też pamiętać o pewnym minusie związanym z przenoszeniem aplikacji między bazami danych. Jeśli napiszemy sporo zapytań w czystym SQL-u może się okazać, że wprowadzimy też coś co specyficznego co jest zgodne ze standardem SQL, ale działa tylko pod określoną bazą danych np. Oracle. Przeniesienie całej aplikacji np. na Postgresa może spowodować, że niektóre zapytania przestaną działać jak należy albo wręcz w ogóle przestaną działać. Dlatego sugerujemy, żeby ograniczyć stosowanie tych zapytań do niezbędnego minimum, albo przynajmniej mieć pełną świadomość konsekwencji jakie to rozwiązanie za sobą niesie.
Używamy w StartAPPa


Zapytania natywne są przez nas używane w kodzie kursu Wykres Danych, który zawiera metody wyciągające z bazy dane statystyczne na potrzeby ich graficznej prezentacji na wykresach.
W tej strefie znajdziesz wszystko co niezbędne, aby komfortowo uczyć się Hibernate'a. Doskonale opisany kod nie zawiera zbędnych komplikacji, tylko samą esencję w postaci praktycznych przykładów. Tutaj odnajdziesz wszystko co jest istotne w danym temacie. Otrzymujesz pakiet złożony z kilku projektów wraz z obszernym wytłumaczeniem kodu.
Linki
https://docs.spring.io/spring-data/jpa/docs/1.11.16.RELEASE/reference/html/#jpa.query-methods.at-query
https://stackoverflow.com/questions/38349930/spring-data-and-native-query-with-pagination

Stale się rozwijamy, a więc bądź na bieżąco!
Na ten adres będziemy przesyłać informacje o ważniejszych aktualizacjach, a także o nowych materiałach pojawiających się na stronie.
Polub nas na Facebooku:
Nasi partnerzy:
Javappa to również profesjonalne usługi programistyczne oparte o technologie JAVA. Jeśli chesz nawiązać z nami kontakt w celu uzyskania doradztwa bądź stworzenia aplikacji webowej powinieneś poznać nasze doświadczenia.
Kliknij O nas .