Spring Data JPA - Zapytania wbudowane

Zapytania wbudowane są bardzo mocną stroną Spring Data JPA. Umożliwiają one nam tworzenie zapytań w oparciu o mechanizmy składania zapytań z nazwy akcji (find...By, read...By, query...By, count...By, get...By, delete...By) oraz części własnej w postaci nazw pól encji, np.:
User findByToken(String token);

Long countByLastname(String lastname);

Stream<User> readByEmail(String email);

Long deleteByLastname(String lastname);

List<Item> findFirst10ByLastname(String lastname, Sort sort);

List<Item> findTop3ByLastname(String lastname, Sort sort);

Jak widać możemy też używać wyrażeń limitujących takich jak Top and First. Należy wtedy podać liczbę rekordów jakie chcemy otrzymać oraz kierunek sortowania.

Dodajmy, że bardzo ważne jest to jakiej używamy wersji Spring Boota (a tym samym Spring Data JPA). Niektóre zapytania (np. readByEmail) będą działały dopiero od wersji Spring Boota 1.5.x, który posiada podpiętą zależność do Spring Data JPA 1.11.x. Niemniej większość z powyższych zapytań będzie działała już na Spring Boot 1.4.x.

Lista słów kluczowych

Metody wbudowane mogą być nawet bardziej złożone niż to co widzimy w przykładzie wyżej. W celu zwiększenia użyteczności takich metod wprowadzone zostały do użycia słowa kluczowe, dzięki którym zyskujemy dużo szersze możliwości konstruowania zapytań. Poniższa tabela prezentuje te możliwości wraz z informacją mówiącą o tym jak są tłumaczone podane metody na zapytania JPQL.
Słowo kluczowe Przykład Tłumaczenie na JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,
findByFirstnameIs,findByFirstnameEquals
… where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith where x.firstname like ?1
EndingWith findByFirstnameEndingWith where x.firstname like ?1
Containing findByFirstnameContaining where x.firstname like ?1
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection<Age> ages) … where x.age not in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

ItemRepository z findByToken

Na koniec przedstawiamy przykład użycia, pokazany w szerszym kontekście. Chcemy więc stworzyć metodę zapytania findByToken, która ma nam pobrać użytkownika na podstawie tokenu, wygenerowanego wcześniej dla tego użytkownika, w trakcie procesu resetowania hasła w systemie. Użytkownik zostanie dzięki temu zidentyfikowany, co pozwoli nam na zapisanie przesłanego przez niego nowego hasła w systemie. Tworzymy więc interfejs UserRepository, w którym wprowadzamy naszą metodę:
public interface UserRepository extends JpaRepository<User, Long> {
    
    ...
    
    User findByToken(String token);
    
    ...
}
Następnie wprowadzamy serwis UserServiceImpl i wstrzykujemy do niego obiekt interfejsu UserRepository. W klasie serwisu dodajemy metodę saveNewPassword, w której używamy naszej nowej metody z repozytorium:
@Service
public class UserServiceImpl implements UserService {
    
    private UserRepository userRepository;
    
    ...
    
    @Autowired
    public UserServiceImpl(UserRepository userRepository, EmailSender emailSender, MD5Encoder ...) {
        this.userRepository = userRepository;
        ...
    }

    @Override
    public NewPasswordResponseDTO saveNewPassword(NewPasswordRequestDTO newPasswordRequestDTO) 
                                                                                 throws Exception {
    
        User user = userRepository.findByToken(newPasswordRequestDTO.getToken());
        validate(user, "Token is ivalid or expired");
    
        ...
    
        return newPasswordResponseDTO;
    }
}
Nasza rekomendacja
Generalnie jest tak, że kompilator pilnuje nas abyśmy np. nie odwołali się do nieistniejącego pola w encji, albo żebyśmy nie użyli słowa kluczowego, którego Spring Data nie dostarcza. Może się jednak zdarzyć, że z przyzwyczajenia, z czasem za nadto zaufamy kompilatorowi, przez co delikatna korekta w pobieraniu danych

z:
User findByEmail(String email);
na:
User findByFirstName(String firstName);
skończy się tym, że kompilator nadal będzie zadowolony, ale JPA już niekoniecznie. O ile findByEmail raczej zawsze będzie zwracać jednego użytkownika, o tyle wyszukiwanie wykonane z udziałem imienia prędzej czy później zwróci nam błąd:
Caused by: javax.persistence.NonUniqueResultException: result returns more than one elements
    at org.hibernate.jpa.internal.QueryImpl.getSingleResult(QueryImpl.java:539)
    at org.hibernate.jpa.criteria.compile.CriteriaQueryTypeQueryAdapter.getSingleResult(CriteriaQueryTypeQueryAdapter.java:54)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:375)
    at com.sun.proxy.$Proxy115.getSingleResult(Unknown Source)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:210)
W tym przypadku musimy oczywiście zmienić typ zwracany przez metodę z klasy User na listę klas User:
List<User> findByFirstName(String firstName);
Takie rzeczy się zdarzają i to nawet po latach praktyki. Bądźmy zatem czujni, bo to się przy tych zapytaniach przydaje. :)
Używamy w StartAPPa


Przykład opisany w paragrafie ItemRepository z findByToken pochodzi z naszej aplikacji i można go znaleźć w kursie Login & Reset (przykład dotyczy części procesu resetowania hasła przez użytkownika).
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/#repositories.query-methods.details

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 .