Spring Data JPA @ Query

1. Oversigt

Spring Data giver mange måder at definere en forespørgsel, som vi kan udføre. En af disse er @Forespørgsel kommentar.

I denne vejledning demonstrerer vi hvordan man bruger @Forespørgsel kommentar i Spring Data JPA for at udføre både JPQL og native SQL-forespørgsler.

Vi viser også, hvordan man opbygger en dynamisk forespørgsel, når @Forespørgsel kommentar er ikke nok.

2. Vælg Forespørgsel

For at definere SQL, der skal udføres for en Spring Data repository-metode, kan vi kommentere metoden med @Forespørgsel kommentar - dens værdi attribut indeholder den JPQL eller SQL, der skal udføres.

Det @Forespørgsel annotering har forrang over navngivne forespørgsler, som er kommenteret med @NamedQuery eller defineret i en orm.xml fil.

Det er en god tilgang at placere en forespørgselsdefinition lige over metoden inde i lageret snarere end inde i vores domænemodel som navngivne forespørgsler. Datalageret er ansvarlig for vedholdenhed, så det er et bedre sted at gemme disse definitioner.

2.1. JPQL

Forespørgselsdefinitionen bruger som standard JPQL.

Lad os se på en simpel opbevaringsmetode, der returnerer aktiv Bruger enheder fra databasen:

@Query ("VÆLG u FRA bruger u HVOR u.status = 1") Samling findAllActiveUsers (); 

2.2. Hjemmehørende

Vi kan også bruge native SQL til at definere vores forespørgsel. Alt hvad vi skal gøre er indstil værdien af nativeQuery attribut til rigtigt og definer den oprindelige SQL-forespørgsel i værdi kommentarens attribut:

@Query (værdi = "VÆLG * FRA BRUGERE u HVOR u.status = 1", nativeQuery = sand) Samling findAllActiveUsersNative (); 

3. Definer rækkefølge i en forespørgsel

Vi kan videregive en ekstra parameter af typen Sortere til en Spring Data-metodedeklaration, der har @Forespørgsel kommentar. Det oversættes til BESTIL BY klausul, der sendes til databasen.

3.1. Sortering efter JPA-leverede og afledte metoder

For de metoder, vi kommer ud af kassen som f.eks findAll (Sort) eller dem, der genereres ved parsing af metodesignaturer, vi kan kun bruge objektegenskaber til at definere vores sortering:

userRepository.findAll (ny sortering (Sort.Direction.ASC, "navn")); 

Forestil dig nu, at vi vil sortere efter længden på en navneegenskab:

userRepository.findAll (ny sortering ("LENGDE (navn)")); 

Når vi udfører ovenstående kode, modtager vi en undtagelse:

org.springframework.data.mapping.PropertyReferenceException: Ingen egenskab LENGDE (navn) fundet for typen Bruger!

3.2. JPQL

Når vi bruger JPQL til en definition af forespørgsler, kan Spring Data håndtere sortering uden problemer - alt hvad vi skal gøre er at tilføje en metodeparameter af typen Sortere:

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

Vi kan kalde denne metode og videregive en Sortere parameter, som vil ordne resultatet efter navn ejendommen til Bruger objekt:

userRepository.findAllUsers (ny sortering ("navn"));

Og fordi vi brugte @Forespørgsel kommentar, kan vi bruge den samme metode til at få den sorterede liste over Brugere efter længden af ​​deres navne:

userRepository.findAllUsers (JpaSort.unsafe ("LENGDE (navn)")); 

Det er afgørende, at vi bruger JpaSort.unsafe () at oprette en Sortere objektinstans.

Når vi bruger:

ny sortering ("LÆNGDE (navn)"); 

så modtager vi nøjagtig den samme undtagelse, som vi så ovenfor for findAll () metode.

Når Spring Data opdager det usikre Sortere bestil en metode, der bruger @Forespørgsel annotation, så tilføjer den bare sorteringsklausulen til forespørgslen - den springer over, om ejendommen, der skal sorteres efter, hører til domænemodellen.

3.3. Hjemmehørende

Når @Forespørgsel annotation bruger native SQL, så er det ikke muligt at definere en Sortere.

Hvis vi gør det, modtager vi en undtagelse:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Kan ikke bruge native forespørgsler med dynamisk sortering og / eller pagination

Som undtagelsen siger, understøttes sorteringen ikke til indfødte forespørgsler. Fejlmeddelelsen giver os et tip om, at paginering også vil medføre en undtagelse.

Der er dog en løsning, der muliggør paginering, og vi dækker det i næste afsnit.

4. Paginering

Pagination giver os mulighed for kun at returnere en delmængde af et helt resultat i en Side. Dette er f.eks. Nyttigt, når du navigerer gennem flere sider med data på en webside.

En anden fordel ved pagination er, at mængden af ​​data, der sendes fra server til klient, minimeres. Ved at sende mindre stykker data kan vi generelt se en forbedring i ydeevnen.

4.1. JPQL

Brug af pagination i JPQL-forespørgselsdefinitionen er ligetil:

@Query (værdi = "VÆLG u FRA Bruger u BESTIL EFTER id") Side findAllUsersWithPagination (Sider, der kan sides); 

Vi kan passere en PageRequest parameter for at få en side med data.

Pagination understøttes også til indfødte forespørgsler, men kræver lidt ekstra arbejde.

4.2. Hjemmehørende

Vi kan aktivere pagination for indfødte forespørgsler ved at erklære en yderligere attribut countQuery.

Dette definerer SQL, der skal udføres for at tælle antallet af rækker i hele resultatet:

@Query (værdi = "VÆLG * FRA BRUGERE BESTIL EFTER id", countQuery = "VÆLG antal (*) FRA brugere", nativeQuery = sand) Side findAllUsersWithPagination (Sidesidet side);

4.3. Spring Data JPA-versioner før 2.0.4

Ovenstående løsning til indfødte forespørgsler fungerer fint til Spring Data JPA version 2.0.4 og nyere.

Forud for denne version, når vi prøver at udføre en sådan forespørgsel, modtager vi den samme undtagelse, som vi beskrev i det forrige afsnit om sortering.

Vi kan løse dette ved at tilføje en ekstra parameter til pagination i vores forespørgsel:

@Query (værdi = "VÆLG * FRA BRUGERE BESTIL EFTER id \ n-- #pageable \ n", countQuery = "VÆLG antal (*) FRA brugere", nativeQuery = sand) Side findAllUsersWithPagination (Pageable pageable);

I eksemplet ovenfor tilføjer vi "\ n– #pageable \ n" som pladsholder for sideparameteren. Dette fortæller Spring Data JPA, hvordan man analyserer forespørgslen og indsætter den side, der kan sættes. Denne løsning fungerer for H2 database.

Vi har dækket, hvordan man opretter enkle udvalgte forespørgsler via JPQL og native SQL. Dernæst viser vi, hvordan man definerer yderligere parametre.

5. Indekserede forespørgselsparametre

Der er to mulige måder, hvorpå vi kan overføre metodeparametre til vores forespørgsel: indekserede og navngivne parametre.

I dette afsnit dækker vi indekserede parametre.

5.1. JPQL

For indekserede parametre i JPQL vil Spring Data videregiv metodeparametre til forespørgslen i samme rækkefølge som de vises i metodedeklarationen:

@Query ("SELECT u FROM User u WHERE u.status =? 1") User findUserByStatus (Integer status); @Query ("VÆLG u FRA bruger u HVOR u.status =? 1 og u.navn =? 2") Bruger findUserByStatusAndName (Heltalsstatus, strengnavn); 

For ovenstående forespørgsler er status metodeparameter tildeles forespørgselsparameteren med indeks 1, og navn metodeparameter tildeles forespørgselsparameteren med indeks 2.

5.2. Hjemmehørende

Indekserede parametre for de oprindelige forespørgsler fungerer nøjagtigt på samme måde som for JPQL:

@Query (værdi = "VÆLG * FRA brugere u HVOR u.status =? 1", nativeQuery = sand) Bruger findUserByStatusNative (Heltalsstatus);

I det næste afsnit viser vi en anden tilgang: videregivelse af parametre via navn.

6. Navngivne parametre

Det kan vi også videregiv metodeparametre til forespørgslen ved hjælp af navngivne parametre. Vi definerer disse ved hjælp af @Param kommentar inde i vores depotmetodedeklaration.

Hver parameter kommenteres med @Param skal have en værdistreng, der matcher det tilsvarende JPQL- eller SQL-forespørgselsparameternavn. En forespørgsel med navngivne parametre er lettere at læse og er mindre tilbøjelig til fejl, hvis forespørgslen skal omformuleres.

6.1. JPQL

Som nævnt ovenfor bruger vi @Param kommentar i metodedeklarationen for at matche parametre defineret ved navn i JPQL med parametre fra metodedeklarationen:

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

Bemærk, at vi i ovenstående eksempel definerede vores SQL-forespørgsel og metodeparametre til at have de samme navne, men det er ikke nødvendigt, så længe værdistrengene er de samme:

@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. Hjemmehørende

Til definitionen af ​​den oprindelige forespørgsel er der ingen forskel i, hvordan vi sender en parameter via navnet til forespørgslen sammenlignet med JPQL - vi bruger @Param kommentar:

@Query (værdi = "VÆLG * FRA brugere u HVOR u.status =: status og u.name =: navn", nativeQuery = sand) Bruger findUserByStatusAndNameNamedParamsNative (@Param ("status") Heltalsstatus, @Param ("navn" ) Navn på streng);

7. Samlingsparameter

Lad os overveje sagen, når hvor klausul i vores JPQL- eller SQL-forespørgsel indeholder I (eller IKKE I) nøgleord:

VÆLG u FRA Bruger u HVOR u.navn IN: navne

I dette tilfælde kan vi definere en forespørgselsmetode, der tager Kollektion som parameter:

@Query (værdi = "VÆLG u FRA Bruger u HVOR u.navn IN: navne") Liste findUserByNameList (@Param ("names") Samlingsnavne);

Da parameteren er en Kollektion, det kan bruges med Liste, HashSet, etc.

Dernæst viser vi, hvordan du ændrer data med @Ændring kommentar.

8. Opdater forespørgsler med @Modifying

Vi kan brug @Forespørgsel kommentar for at ændre databasens tilstand ved også at tilføje @Ændring kommentar til arkivmetoden.

8.1. JPQL

Databasemetoden, der ændrer dataene, har to forskelle i forhold til Vælg forespørgsel - det har @Modifying kommentar og selvfølgelig bruger JPQL-forespørgslen opdatering i stedet for Vælg:

@Modifying @Query ("opdater bruger Bruger du indstiller u.status =: status hvor u.navn =: navn") int updateUserSetStatusForName (@Param ("status") Heltalsstatus, @Param ("navn") Navn på streng); 

Returværdien definerer, hvor mange rækker udførelsen af ​​forespørgslen opdateres. Både indekserede og navngivne parametre kan bruges i opdateringsforespørgsler.

8.2. Hjemmehørende

Vi kan ændre databasens tilstand også med en indfødt forespørgsel. Vi skal bare tilføje @Modifying kommentar:

@Modifying @Query (værdi = "opdater brugere u indstiller u.status =? Hvor u.name =?", NativeQuery = true) int updateUserSetStatusForNameNative (Heltalsstatus, strengnavn);

8.3. Indsatser

For at udføre en indsatsoperation skal vi begge anvende @Modifying og brug en indfødt forespørgsel, da INSERT ikke er en del af JPA-grænsefladen:

@Modifying @Query (værdi = "indsæt i brugere (navn, alder, e-mail, status) værdier (: navn,: alder,: e-mail,: status)", nativeQuery = sand) ugyldig insertUser (@Param ("navn") String name, @Param ("age") Integer age, @Param ("status") Integer status, @Param ("email") Streng email);

9. Dynamisk forespørgsel

Ofte støder vi på behovet for at opbygge SQL-sætninger baseret på betingelser eller datasæt, hvis værdier kun er kendt under kørsel. Og i disse tilfælde kan vi ikke bare bruge en statisk forespørgsel.

9.1. Eksempel på en dynamisk forespørgsel

Lad os for eksempel forestille os en situation, hvor vi skal vælge alle de brugere, hvis e-mail er SYNES GODT OM et fra et sæt defineret ved runtime - e-mail1, e-mail2, …, emailn:

VÆLG u FRA Bruger u HVOR u.email LIKE '% email1%' eller u.email LIKE '% email2%' ... eller u.email LIKE '% emailn%'

Da sættet er konstrueret dynamisk, kan vi ikke ved kompileringstidspunktet vide, hvor mange SYNES GODT OM klausuler, der skal tilføjes.

I dette tilfælde, vi kan ikke bare bruge @Forespørgsel kommentar, da vi ikke kan levere en statisk SQL-sætning.

I stedet for ved at implementere et brugerdefineret sammensat lager kan vi udvide basen JpaRepository funktionalitet og give vores egen logik til opbygning af en dynamisk forespørgsel. Lad os se på, hvordan du gør dette.

9.2. Custom Repositories og JPA Criteria API

Heldigvis for os, Spring giver en måde at udvide basislageret ved hjælp af brugerdefinerede fragmentgrænseflader. Vi kan derefter forbinde dem sammen for at oprette et sammensat lager.

Vi starter med at oprette en brugerdefineret fragmentgrænseflade:

offentlig grænseflade UserRepositoryCustom {List findUserByEmails (Set emails); }

Og så implementerer vi det:

offentlig klasse UserRepositoryCustomImpl implementerer UserRepositoryCustom {@PersistenceContext private EntityManager entityManager; @Override public List findUserByEmails (Set emails) {CriteriaBuilder cb = entityManager.getCriteriaBuilder (); CriteriaQuery-forespørgsel = cb.createQuery (User.class); Root user = query.from (User.class); Sti emailPath = user.get ("email"); Listeprædikater = ny ArrayList (); for (String email: emails) {predicates.add (cb.like (emailPath, email)); } query.select (bruger) .where (cb.or (predicates.toArray (new Predicate [predicates.size ()]))); returnere entityManager.createQuery (forespørgsel) .getResultList (); }}

Som vist ovenfor udnyttede vi JPA Criteria API til at opbygge vores dynamiske forespørgsel.

Vi skal også sørge for at medtage Impl postfix i klassens navn. Foråret vil søge i UserRepositoryCustom implementering som UserRepositoryCustomImpl. Da fragmenter ikke i sig selv er opbevaringssteder, stoler Spring på denne mekanisme for at finde implementeringen af ​​fragmentet.

9.3. Udvidelse af det eksisterende arkiv

Bemærk, at alle forespørgselsmetoder fra afsnit 2 til afsnit 7 findes i UserRepository.

Så nu integrerer vi vores fragment ved at udvide den nye grænseflade i UserRepository:

offentlig grænseflade UserRepository udvider JpaRepository, UserRepositoryCustom {// forespørgselsmetoder fra afsnit 2 - afsnit 7}

9.4. Brug af arkivet

Og endelig kan vi kalde vores dynamiske forespørgselsmetode:

Indstil e-mails = nyt HashSet (); // udfyldning af sættet med et vilkårligt antal elementer userRepository.findUserByEmails (emails); 

Vi har med succes oprettet et sammensat lager og kaldt vores brugerdefinerede metode.

10. Konklusion

I denne artikel dækkede vi flere måder at definere forespørgsler i Spring Data JPA repository-metoder ved hjælp af @Forespørgsel kommentar.

Vi lærte også, hvordan man implementerer et brugerdefineret lager og opretter en dynamisk forespørgsel.

Som altid er de komplette kodeeksempler, der bruges i denne artikel, tilgængelige på GitHub.