Spring Data JPA @Query

1. Overzicht

Spring Data biedt vele manieren om een ​​query te definiëren die we kunnen uitvoeren. Een daarvan is de @Query annotatie.

In deze tutorial demonstreren we hoe u de @Query annotatie in Spring Data JPA om zowel JPQL- als native SQL-query's uit te voeren.

We laten ook zien hoe u een dynamische query maakt wanneer de @Query annotatie is niet genoeg.

2. Selecteer Query

Om SQL te definiëren om uit te voeren voor een Spring Data-repository-methode, kunnen we annoteer de methode met de @Query annotatie - zijn waarde attribuut bevat de JPQL of SQL die moet worden uitgevoerd.

De @Query annotatie heeft voorrang op benoemde query's, die zijn geannoteerd met @NamedQuery of gedefinieerd in een orm.xml het dossier.

Het is een goede benadering om een ​​querydefinitie net boven de methode in de repository te plaatsen in plaats van in ons domeinmodel als benoemde queries. De repository is verantwoordelijk voor persistentie, dus het is een betere plek om deze definities op te slaan.

2.1. JPQL

Standaard gebruikt de querydefinitie JPQL.

Laten we eens kijken naar een eenvoudige repository-methode die actief retourneert Gebruiker entiteiten uit de database:

@Query ("SELECTEER u UIT Gebruiker u WAAR u.status = 1") Verzameling findAllActiveUsers (); 

2.2. Inheems

We kunnen ook native SQL gebruiken om onze query te definiëren. Het enige wat we hoeven te doen is stel de waarde van de nativeQuery toe te schrijven aan waar en definieer de native SQL-query in het waarde attribuut van de annotatie:

@Query (waarde = "SELECTEER * VAN GEBRUIKERS u WAAR u.status = 1", nativeQuery = true) Verzameling findAllActiveUsersNative (); 

3. Definieer de volgorde in een query

We kunnen een extra parameter van het type doorgeven Soort naar een Spring Data-methodedeclaratie die de @Query annotatie. Het wordt vertaald in het BESTEL DOOR clausule die wordt doorgegeven aan de database.

3.1. Sorteren voor door de JPA verstrekte en afgeleide methoden

Voor de methoden die we uit de doos halen, zoals findAll (Sorteren) of degenen die worden gegenereerd door handtekeningen van de ontledingsmethode, we kunnen alleen objecteigenschappen gebruiken om onze sortering te definiëren:

userRepository.findAll (nieuwe sortering (Sort.Direction.ASC, "naam")); 

Stel je nu voor dat we willen sorteren op de lengte van een naameigenschap:

userRepository.findAll (nieuwe sortering ("LENGTH (naam)")); 

Wanneer we de bovenstaande code uitvoeren, ontvangen we een uitzondering:

org.springframework.data.mapping.PropertyReferenceException: geen eigenschap lENGTH (naam) gevonden voor type gebruiker!

3.2. JPQL

Wanneer we JPQL gebruiken voor een querydefinitie, kan Spring Data probleemloos sorteren - we hoeven alleen maar een methodeparameter van het type toe te voegen Soort:

@Query (value = "SELECTEER u VAN Gebruiker u") Lijst findAllUsers (Sorteer sortering); 

We kunnen deze methode noemen en een Soort parameter, die het resultaat rangschikt op basis van de naam eigendom van de Gebruiker voorwerp:

userRepository.findAllUsers (nieuwe sortering ("naam"));

En omdat we de @Query annotatie, kunnen we dezelfde methode gebruiken om de gesorteerde lijst met Gebruikers door de lengte van hun namen:

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

Het is cruciaal dat we JpaSort.onsafe () om een Soort object instantie.

Wanneer we gebruiken:

new Sort ("LENGTH (name)"); 

dan ontvangen we precies dezelfde uitzondering als we hierboven zagen voor de vind alle() methode.

Wanneer Spring Data het onveilige ontdekt Soort bestelling voor een methode die de @Query annotatie, dan voegt het gewoon de sorteerclausule toe aan de query - het slaat de controle over of de eigenschap waarop moet worden gesorteerd tot het domeinmodel behoort.

3.3. Inheems

Wanneer de @Query annotatie maakt gebruik van native SQL, dan is het niet mogelijk om een Soort.

Als we dat doen, ontvangen we een uitzondering:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: kan geen native queries gebruiken met dynamische sortering en / of paginering

Zoals de uitzondering zegt, wordt het sorteren niet ondersteund voor native queries. De foutmelding geeft ons een hint dat paginering ook een uitzondering zal veroorzaken.

Er is echter een oplossing die paginering mogelijk maakt, en we zullen dit in de volgende sectie behandelen.

4. Paginering

Met paginering kunnen we slechts een subset van een heel resultaat retourneren in een Bladzijde. Dit is bijvoorbeeld handig bij het navigeren door meerdere pagina's met gegevens op een webpagina.

Een ander voordeel van paginering is dat de hoeveelheid gegevens die van server naar client wordt verzonden, tot een minimum wordt beperkt. Door kleinere stukjes gegevens te verzenden, kunnen we over het algemeen een verbetering van de prestaties zien.

4.1. JPQL

Het gebruik van paginering in de JPQL-querydefinitie is eenvoudig:

@Query (waarde = "SELECTEER u UIT gebruiker u BESTEL OP ID") Pagina findAllUsersWithPagination (pageable pageable); 

We kunnen een PageRequest parameter om een ​​pagina met gegevens te krijgen.

Paginering wordt ook ondersteund voor native queries, maar vereist wat extra werk.

4.2. Inheems

Wij kunnen schakel paginering voor native queries in door een extra attribuut te declareren countQuery.

Dit definieert de SQL die moet worden uitgevoerd om het aantal rijen in het hele resultaat te tellen:

@Query (waarde = "SELECTEER * VAN GEBRUIKERS ORDER BY id", countQuery = "SELECTEER aantal (*) VAN gebruikers", nativeQuery = true) Pagina findAllUsersWithPagination (Pageable pageable);

4.3. Spring Data JPA-versies vóór 2.0.4

De bovenstaande oplossing voor native queries werkt prima voor Spring Data JPA-versies 2.0.4 en hoger.

Voorafgaand aan die versie, wanneer we proberen een dergelijke query uit te voeren, ontvangen we dezelfde uitzondering die we in de vorige sectie over sorteren hebben beschreven.

We kunnen dit verhelpen door een extra parameter voor paginering in onze zoekopdracht toe te voegen:

@Query (waarde = "SELECTEER * VAN GEBRUIKERS ORDER BY id \ n-- #pageable \ n", countQuery = "SELECTEER count (*) VAN gebruikers", nativeQuery = true) Pagina findAllUsersWithPagination (Pageable pageable);

In het bovenstaande voorbeeld voegen we "\ n– #pageable \ n" toe als tijdelijke aanduiding voor de pagineringparameter. Dit vertelt Spring Data JPA hoe de query moet worden geparseerd en de pageable-parameter moet worden geïnjecteerd. Deze oplossing werkt voor de H2 database.

We hebben besproken hoe u eenvoudige selectiequery's kunt maken via JPQL en native SQL. Vervolgens laten we zien hoe u aanvullende parameters kunt definiëren.

5. Geïndexeerde queryparameters

Er zijn twee mogelijke manieren waarop we methodeparameters kunnen doorgeven aan onze query: geïndexeerde en benoemde parameters.

In deze sectie behandelen we geïndexeerde parameters.

5.1. JPQL

Voor geïndexeerde parameters in JPQL zal Spring Data geef methodeparameters door aan de query in dezelfde volgorde waarin ze voorkomen in de methodedeclaratie:

@Query ("SELECTEER u UIT Gebruiker u WAAR u.status =? 1") Gebruiker findUserByStatus (Integer-status); @Query ("SELECTEER u UIT Gebruiker u WAAR u.status =? 1 en u.name =? 2") Gebruiker findUserByStatusAndName (Integer-status, Stringnaam); 

Voor de bovenstaande vragen is het toestand method parameter zal worden toegewezen aan de queryparameter met index 1, en de naam method parameter wordt toegewezen aan de queryparameter met index 2.

5.2. Inheems

Geïndexeerde parameters voor de native queries werken op precies dezelfde manier als voor JPQL:

@Query (waarde = "SELECTEER * VAN gebruikers u WAAR u.status =? 1", nativeQuery = true) Gebruiker findUserByStatusNative (geheel getalstatus);

In de volgende sectie laten we een andere benadering zien: parameters doorgeven via naam.

6. Benoemde parameters

We kunnen ook geef methodeparameters door aan de query met behulp van benoemde parameters. We definiëren deze met behulp van de @Param annotatie in onze repository-methode-declaratie.

Elke parameter is geannoteerd met @Param moet een waardetekenreeks hebben die overeenkomt met de corresponderende JPQL- of SQL-queryparameternaam. Een query met benoemde parameters is gemakkelijker te lezen en is minder foutgevoelig voor het geval de query opnieuw moet worden geformuleerd.

6.1. JPQL

Zoals hierboven vermeld, gebruiken we de @Param annotatie in de methode-declaratie om parameters te matchen die zijn gedefinieerd door naam in JPQL met parameters uit de methode-declaratie:

@Query ("SELECT u UIT Gebruiker u WAAR u.status =: status en u.name =: naam") Gebruiker findUserByStatusAndNameNamedParams (@Param ("status") Geheel getal status, @Param ("naam") Stringnaam); 

Merk op dat we in het bovenstaande voorbeeld onze SQL-query- en methodeparameters hebben gedefinieerd om dezelfde namen te hebben, maar dit is niet vereist zolang de waardetekenreeksen hetzelfde zijn:

@Query ("SELECTEER u UIT Gebruiker u WAAR u.status =: status en u.name =: naam") Gebruiker findUserByUserStatusAndUserName (@Param ("status") Geheel getal userStatus, @Param ("naam") String gebruikersnaam); 

6.2. Inheems

Voor de native querydefinitie is er geen verschil in hoe we een parameter via de naam doorgeven aan de query in vergelijking met JPQL - we gebruiken de @Param annotatie:

@Query (value = "SELECT * FROM Users u WHERE u.status =: status en u.name =: name", nativeQuery = true) Gebruiker findUserByStatusAndNameNamedParamsNative (@Param ("status") Status geheel getal, @Param ("name" ) String naam);

7. Verzamelingsparameter

Laten we eens kijken naar het geval waarin de waar clausule van onze JPQL- of SQL-query bevat de IN (of NIET IN) trefwoord:

SELECTEER u UIT Gebruiker u WAAR u.naam IN: namen

In dit geval kunnen we een querymethode definiëren die duurt Verzameling als parameter:

@Query (waarde = "SELECTEER u UIT Gebruiker u WAAR u.name IN: namen") Lijst findUserByNameList (@Param ("namen") Verzamelingsnamen);

Omdat de parameter een Verzameling, het kan worden gebruikt met Lijst, HashSet, enz.

Vervolgens laten we zien hoe u gegevens kunt wijzigen met de @Wijzigen annotatie.

8. Update zoekopdrachten met @Wijzigen

Wij kunnen gebruik de @Vraag annotatie om de staat van de database te wijzigen door ook de @ toe te voegenWijzigen annotatie naar de repository-methode.

8.1. JPQL

De repository-methode die de gegevens wijzigt, heeft twee verschillen in vergelijking met de selecteer query - het heeft de @Wijzigen annotatie en, natuurlijk, de JPQL-query gebruikt bijwerken in plaats van selecteer:

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

De geretourneerde waarde geeft aan hoeveel rijen de uitvoering van de query is bijgewerkt. Zowel geïndexeerde als benoemde parameters kunnen worden gebruikt in update-query's.

8.2. Inheems

We kunnen de staat van de database ook wijzigen met een native query. We hoeven alleen de @Wijzigen annotatie:

@Modifying @Query (waarde = "update gebruikers u u.status =? Waar u.name =?", NativeQuery = true) int updateUserSetStatusForNameNative (geheel getalstatus, tekenreeksnaam);

8.3. Inzetstukken

Om een ​​invoegbewerking uit te voeren, moeten we beide toepassen @Wijzigen en gebruik een native query omdat INSERT geen deel uitmaakt van de JPA-interface:

@Modifying @Query (waarde = "invoegen in gebruikers (naam, leeftijd, e-mail, status) waarden (: naam,: leeftijd,: e-mail,: status)", nativeQuery = true) void insertUser (@Param ("naam") String naam, @Param ("leeftijd") Geheel getal leeftijd, @Param ("status") Geheel getal status, @Param ("email") String e-mail);

9. Dynamische zoekopdracht

Vaak zullen we de noodzaak tegenkomen om SQL-instructies te bouwen op basis van voorwaarden of gegevenssets waarvan de waarden alleen bekend zijn tijdens runtime. En in die gevallen kunnen we niet zomaar een statische query gebruiken.

9.1. Voorbeeld van een dynamische zoekopdracht

Laten we ons bijvoorbeeld een situatie voorstellen waarin we alle gebruikers moeten selecteren wiens e-mail is LEUK VINDEN één uit een set gedefinieerd tijdens runtime - e-mail 1, e-mail2, …, emailn:

SELECTEER u UIT Gebruiker u WAAR u.email ALS '% email1%' of u.email ALS '% email2%' ... of u.email ALS '% emailn%'

Omdat de set dynamisch is opgebouwd, kunnen we tijdens het compileren niet weten hoeveel LEUK VINDEN clausules om toe te voegen.

In dit geval, we kunnen niet alleen de @Query annotatie omdat we geen statische SQL-instructie kunnen geven.

In plaats daarvan kunnen we de basis uitbreiden door een aangepaste samengestelde repository te implementeren JpaRepository functionaliteit en onze eigen logica bieden voor het bouwen van een dynamische query. Laten we eens kijken hoe u dit kunt doen.

9.2. Aangepaste opslagplaatsen en de JPA Criteria API

Gelukkig voor ons, Spring biedt een manier om de basisrepository uit te breiden door het gebruik van aangepaste fragmentinterfaces. We kunnen ze vervolgens aan elkaar koppelen om een ​​samengestelde repository te maken.

We beginnen met het maken van een aangepaste fragmentinterface:

openbare interface UserRepositoryCustom {List findUserByEmails (Set e-mails); }

En dan implementeren we het:

openbare klasse UserRepositoryCustomImpl implementeert UserRepositoryCustom {@PersistenceContext privé EntityManager entityManager; @Override openbare lijst findUserByEmails (e-mails instellen) {CriteriaBuilder cb = entityManager.getCriteriaBuilder (); CriteriaQuery-query = cb.createQuery (User.class); Hoofdgebruiker = query.from (User.class); Pad emailPath = user.get ("email"); Lijstpredikaten = nieuwe ArrayList (); voor (String e-mail: e-mails) {predicates.add (cb.like (emailPath, e-mail)); } query.select (gebruiker) .where (cb.or (predicates.toArray (new Predicaat [predicates.size ()]))); return entityManager.createQuery (query) .getResultList (); }}

Zoals hierboven weergegeven, hebben we de JPA Criteria API gebruikt om onze dynamische query op te bouwen.

We moeten er ook voor zorgen dat de Impl postfix in de klassenaam. De lente zal het UserRepositoryCustom implementatie als UserRepositoryCustomImpl. Omdat fragmenten op zichzelf geen repositories zijn, vertrouwt Spring op dit mechanisme om de fragmentimplementatie te vinden.

9.3. Uitbreiding van de bestaande repository

Merk op dat alle zoekmethoden van sectie 2 tot en met sectie 7 zich in de UserRepository.

Dus nu gaan we ons fragment integreren door de nieuwe interface uit te breiden in het UserRepository:

openbare interface UserRepository breidt JpaRepository, UserRepositoryCustom {// zoekmethoden uit sectie 2 - sectie 7} uit

9.4. Met behulp van de repository

En tot slot kunnen we onze dynamische zoekmethode noemen:

Set e-mails = nieuwe HashSet (); // de set vullen met een willekeurig aantal items userRepository.findUserByEmails (e-mails); 

We hebben met succes een samengestelde repository gemaakt en onze aangepaste methode genoemd.

10. Conclusie

In dit artikel hebben we verschillende manieren besproken om query's te definiëren in Spring Data JPA-repository-methoden met behulp van de @Query annotatie.

We hebben ook geleerd hoe we een aangepaste repository kunnen implementeren en een dynamische query kunnen maken.

Zoals altijd zijn de volledige codevoorbeelden die in dit artikel worden gebruikt, beschikbaar op GitHub.