Spring Data JPA - Zapytania Wbudowane

Zapytania Wbudowane (Custom Queries) umożliwiają tworzenie zapytań w oparciu o mechanizm łączenia nazw metod (find...By, read...By, query...By, count...By, get...By, delete...By) z nazwami 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.

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)

Metoda findByToken

Na zakończenie, prezentujemy praktyczne zastosowanie przedstawionej metody w szerszym kontekście. Naszym celem jest stworzenie metody zapytania findByToken, która umożliwi pobranie danych użytkownika na podstawie unikalnego tokenu, wygenerowanego dla niego w procesie resetowania hasła. Dzięki tej metodzie użytkownik zostanie zidentyfikowany, co umożliwi zapisanie nowego hasła przesłanego przez niego do systemu. W tym celu definiujemy interfejs UserRepository, w którym wprowadzamy naszą metodę:
public interface UserRepository extends JpaRepository<User, Long> {
    
    ...
    
    User findByToken(String token);
    
    ...
}
Kolejnym krokiem jest utworzenie serwisu UserServiceImpl, do którego wstrzykujemy instancję UserRepository. W klasie serwisu implementujemy metodę saveNewPassword, wykorzystując wcześniej utworzoną metodę findByToken z naszego 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) {
    
        User user = userRepository.findByToken(newPasswordRequestDTO.getToken());
        validate(user, "Token is ivalid or expired");
    
        ...
    
        return newPasswordResponseDTO;
    }
}
Rekomendacja
Kompilator często pełni rolę ochronną, uniemożliwiając dostęp do nieistniejących pól lub słów kluczowych, które Spring Data nie obsługuje. Niemniej jednak, nadmierna pewność siebie wobec kompilatora może prowadzić do subtelnych błędów w operacjach pobierania danych. Na przykład, zmiana metody

z:
User findByEmail(String email);
na:
User findByFirstName(String firstName);
może nie spowodować błędu kompilacji, ale może prowadzić do problemów z JPA, ponieważ nie odpowiada to strukturze danych w bazie. Dlatego ważne jest, aby być ostrożnym i świadomym zmian w kodzie, nawet jeśli kompilator nie zgłasza błędów. 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);
Praktyka


Przykład opisany w paragrafie "Metoda findByToken" pochodzi z naszej aplikacji.
Zdjęcie autora
Autor: Jarek Klimas
Data: 03 stycznia 2024
Labele: Backend, Podstawowy, Java
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.
Topowe Materiały
Spring IO: JPA Query Methods
Baeldung: Spring Data JPA – Adding a Method in All Repositories

Udemy: [NEW] Spring Boot 3, Spring 6 & Hibernate for Beginners  —  polskie napisy

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: stackshare
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 .


Pozycjonowanie stron: Grupa TENSE