Inleiding tot Querydsl

1. Inleiding

Dit is een inleidend artikel om u op weg te helpen met de krachtige Querydsl API voor gegevenspersistentie.

Het doel is hier om u de praktische tools te geven om Querydsl aan uw project toe te voegen, de structuur en het doel van de gegenereerde klassen te begrijpen en een basiskennis te krijgen over hoe u typeveilige databasequery's schrijft voor de meest voorkomende scenario's.

2. Het doel van Querydsl

Object-relationele mapping-frameworks vormen de kern van Enterprise Java. Deze compenseren de mismatch tussen objectgeoriënteerde benadering en relationeel databasemodel. Ze stellen ontwikkelaars ook in staat om schonere en beknoptere persistentiecode en domeinlogica te schrijven.

Een van de moeilijkste ontwerpkeuzes voor een ORM-framework is echter de API voor het bouwen van correcte en typeveilige query's.

Een van de meest gebruikte Java ORM-frameworks, Hibernate (en ook nauw verwante JPA-standaard), stelt een op tekenreeksen gebaseerde querytaal HQL (JPQL) voor die erg lijkt op SQL. De voor de hand liggende nadelen van deze benadering zijn het gebrek aan typeveiligheid en het ontbreken van statische querycontrole. Ook in meer complexe gevallen (bijvoorbeeld wanneer de query tijdens runtime moet worden geconstrueerd, afhankelijk van bepaalde omstandigheden), omvat het bouwen van een HQL-query doorgaans het aaneenschakelen van strings, wat meestal erg onveilig en foutgevoelig is.

De JPA 2.0-standaard bracht een verbetering met zich mee in de vorm van Criteria Query API - een nieuwe en typeveilige methode voor het bouwen van queries die gebruik maakte van metamodelklassen die werden gegenereerd tijdens de voorbewerking van annotaties. Helaas was de Criteria Query API baanbrekend in zijn essentie en eindigde het erg uitgebreid en praktisch onleesbaar. Hier is een voorbeeld uit de Jakarta EE-zelfstudie voor het genereren van een zo eenvoudig mogelijke zoekopdracht SELECTEER p UIT Huisdier p:

EntityManager em = ...; CriteriaBuilder cb = em.getCriteriaBuilder (); CriteriaQuery cq = cb.createQuery (Pet.class); Root pet = cq.from (Pet.class); cq.select (huisdier); TypedQuery q = em.createQuery (cq); Lijst allPets = q.getResultList ();

Geen wonder dat er al snel een meer adequate Querydsl-bibliotheek ontstond, gebaseerd op hetzelfde idee van gegenereerde metadataklassen, maar geïmplementeerd met een vloeiende en leesbare API.

3. Genereren van Querydsl-klassen

Laten we beginnen met het genereren en verkennen van de magische metaklassen die verantwoordelijk zijn voor de vloeiende API van Querydsl.

3.1. Querydsl toevoegen aan Maven Build

Het opnemen van Querydsl in uw project is net zo eenvoudig als het toevoegen van verschillende afhankelijkheden aan uw buildbestand en het configureren van een plug-in voor het verwerken van JPA-annotaties. Laten we beginnen met de afhankelijkheden. De versie van Querydsl-bibliotheken moet worden uitgepakt naar een aparte eigenschap binnen het sectie, als volgt (voor de nieuwste versie van Querydsl-bibliotheken, controleer de Maven Central-repository):

 4.1.3 

Voeg vervolgens de volgende afhankelijkheden toe aan het sectie van uw pom.xml het dossier:

  com.querydsl querydsl-apt $ {querydsl.version} verstrekt com.querydsl querydsl-jpa $ {querydsl.version} 

De querydsl-apt afhankelijkheid is een annotatieverwerkingsprogramma (APT) - implementatie van overeenkomstige Java API waarmee annotaties in bronbestanden kunnen worden verwerkt voordat ze doorgaan naar de compilatiefase. Deze tool genereert de zogenaamde Q-types - klassen die direct betrekking hebben op de entiteitsklassen van uw applicatie, maar voorafgegaan worden door de letter Q. Als u bijvoorbeeld een Gebruiker klasse gemarkeerd met de @Entiteit annotatie in uw applicatie, dan zal het gegenereerde Q-type in een QUser.java bron bestand.

De voorzien reikwijdte van de querydsl-apt afhankelijkheid betekent dat deze jar alleen tijdens het bouwen beschikbaar moet worden gemaakt, maar niet in het toepassingsartefact moet worden opgenomen.

De querydsl-jpa-bibliotheek is de Querydsl zelf, ontworpen om samen met een JPA-applicatie te worden gebruikt.

Om annotatieverwerkingsplug-in te configureren die gebruikmaakt van querydsl-apt, voeg de volgende plugin-configuratie toe aan je pom - binnen het element:

 com.mysema.maven apt-maven-plugin 1.1.3 proces doel / gegenereerde-bronnen / java com.querydsl.apt.jpa.JPAAnnotationProcessor 

Deze plug-in zorgt ervoor dat de Q-types worden gegenereerd tijdens het procesdoel van Maven build. De outputDirectory configuratie-eigenschap verwijst naar de directory waar de Q-type bronbestanden zullen worden gegenereerd. De waarde van deze eigenschap komt later van pas als je de Q-files gaat verkennen.

U moet deze map ook toevoegen aan de bronmappen van het project, als uw IDE dit niet automatisch doet - raadpleeg de documentatie voor uw favoriete IDE voor informatie over hoe u dat moet doen.

Voor dit artikel gebruiken we een eenvoudig JPA-model van een blogservice, bestaande uit Gebruikers en hun Blog berichten met een een-op-veel-relatie tussen hen:

@Entity openbare klasse Gebruiker {@Id @GeneratedValue privé Lange id; privé String-login; privé Boolean uitgeschakeld; @OneToMany (cascade = CascadeType.PERSIST, mappedBy = "gebruiker") privé Set blogPosts = nieuwe HashSet (0); // getters and setters} @Entity public class BlogPost {@Id @GeneratedValue privé Lange id; private String-titel; privé String lichaam; @ManyToOne privé gebruiker gebruiker; // getters en setters}

Om Q-types voor uw model te genereren, voert u gewoon het volgende uit:

mvn compileren

3.2. Gegenereerde klassen verkennen

Ga nu naar de map die is opgegeven in het outputDirectory eigenschap van apt-maven-plugin (target / gegenereerde-bronnen / java in ons voorbeeld). U ziet een pakket en een klassenstructuur die rechtstreeks overeenkomt met uw domeinmodel, behalve dat alle klassen beginnen met de letter Q (QUser en QBlogPost in ons geval).

Open het bestand QUser.java. Dit is uw startpunt voor het maken van alle queries die Gebruiker als een hoofdentiteit. Het eerste dat opvalt, is de @Generated annotatie, wat betekent dat dit bestand automatisch is gegenereerd en niet handmatig hoeft te worden bewerkt. Als u een van uw domeinmodelklassen wijzigt, moet u deze uitvoeren mvn compileren opnieuw om alle overeenkomstige Q-types te regenereren.

Afgezien van verschillende QUser constructors die in dit bestand aanwezig zijn, moet u ook letten op een openbare statische laatste instantie van het QUser klasse:

openbare statische laatste QUser gebruiker = nieuwe QUser ("gebruiker");

Dit is het exemplaar dat u in de meeste van uw Querydsl-query's naar deze entiteit kunt gebruiken, behalve wanneer u wat complexere query's moet schrijven, zoals het samenvoegen van verschillende instanties van een tabel in een enkele query.

Het laatste dat moet worden opgemerkt, is dat er voor elk veld van de entiteitsklasse een overeenkomstig *Pad veld in het Q-type, zoals NumberPath-ID, StringPath login en SetPath blogPosts in de QUser class (merk op dat de naam van het veld overeenkomt met Set is meervoud). Deze velden worden gebruikt als onderdelen van de vloeiende query-API die we later zullen tegenkomen.

4. Query's uitvoeren met Querydsl

4.1. Eenvoudig zoeken en filteren

Om een ​​query te maken, hebben we eerst een instantie van een JPAQueryFactory, wat een geprefereerde manier is om het bouwproces te starten. Het enige dat JPAQueryFactory behoeften is een EntityManager, die al beschikbaar zou moeten zijn in uw JPA-toepassing via EntityManagerFactory.createEntityManager () bel of @PersistenceContext injectie.

EntityManagerFactory emf = Persistence.createEntityManagerFactory ("com.baeldung.querydsl.intro"); EntityManager em = entityManagerFactory.createEntityManager (); JPAQueryFactory queryFactory = nieuwe JPAQueryFactory (em);

Laten we nu onze eerste zoekopdracht maken:

QUser gebruiker = QUser.user; Gebruiker c = queryFactory.selectFrom (gebruiker) .where (user.login.eq ("David")) .fetchOne ();

Merk op dat we een lokale variabele hebben gedefinieerd QUser user en geïnitialiseerd met QUser.user statische instantie. Dit wordt puur gedaan voor de beknoptheid, u kunt ook de statische gegevens importeren QUser.user veld.

De selecteer uit methode van de JPAQueryFactory begint met het opbouwen van een vraag. We geven het de QUser instantie en ga door met het bouwen van de voorwaardelijke clausule van de query met de .waar() methode. De gebruiker login is een verwijzing naar een StringPath veld van de QUser les die we eerder hebben gezien. De StringPath object heeft ook de .eq () methode die het mogelijk maakt om vloeiend door te gaan met het bouwen van de query door de veldgelijkheidsvoorwaarde op te geven.

Ten slotte, om de waarde uit de database op te halen in persistentiecontext, beëindigen we de bouwketen met de aanroep van de fetchOne () methode. Deze methode keert terug nul als het object niet kan worden gevonden, maar een NonUniqueResultException als er meerdere entiteiten zijn die voldoen aan de .waar() staat.

4.2. Bestellen en groeperen

Laten we nu alle gebruikers in een lijst ophalen, gesorteerd op hun login in oplopende volgorde.

Lijst c = queryFactory.selectFrom (gebruiker) .orderBy (user.login.asc ()) .fetch ();

Deze syntaxis is mogelijk omdat de *Pad klassen hebben de .asc () en .desc () methoden. U kunt ook verschillende argumenten opgeven voor de .orderBy () methode om op meerdere velden te sorteren.

Laten we nu iets moeilijkers proberen. Stel dat we alle berichten op titel moeten groeperen en dubbele titels moeten tellen. Dit wordt gedaan met de .groupBy () clausule. We willen de titels ook ordenen op basis van het resulterende aantal voorkomen.

NumberPath count = Expressions.numberPath (Long.class, "c"); Lijst userTitleCounts = queryFactory.select (blogPost.title, blogPost.id.count (). Als (count)) .from (blogPost) .groupBy (blogPost.title) .orderBy (count.desc ()) .fetch ();

We hebben de titel van de blogpost en het aantal duplicaten geselecteerd, gegroepeerd op titel en vervolgens gesorteerd op geaggregeerd aantal. Merk op dat we eerst een alias hebben gemaakt voor het tellen () veld in het.selecteer () clausule, omdat we ernaar moesten verwijzen in de .orderBy () clausule.

4.3. Complexe zoekopdrachten met joins en subquery's

Laten we alle gebruikers zoeken die een bericht hebben geschreven met de titel 'Hallo wereld!' Voor zo'n zoekopdracht zouden we een inner join kunnen gebruiken. Merk op dat we een alias hebben gemaakt blogpost voor de samengevoegde tabel om ernaar te verwijzen in de .Aan() clausule:

QBlogPost blogPost = QBlogPost.blogPost; Lijst met gebruikers = queryFactory.selectFrom (gebruiker) .innerJoin (user.blogPosts, blogPost) .on (blogPost.title.eq ("Hallo wereld!")) .Fetch ();

Laten we nu proberen hetzelfde te bereiken met een subquery:

Lijst gebruikers = queryFactory.selectFrom (user) .where (user.id.in (JPAExpressions.select (blogPost.user.id) .from (blogPost) .where (blogPost.title.eq ("Hallo wereld!"))) ) .fetch ();

Zoals we kunnen zien, lijken subquery's erg op queries, en ze zijn ook redelijk leesbaar, maar ze beginnen met JPA-uitdrukkingen fabrieksmethoden. Om subquery's met de hoofdquery te verbinden, verwijzen we zoals altijd naar de aliassen die eerder zijn gedefinieerd en gebruikt.

4.4. Gegevens wijzigen

JPAQueryFactory maakt het niet alleen mogelijk om query's te maken, maar ook om records te wijzigen en te verwijderen. Laten we de login van de gebruiker wijzigen en het account uitschakelen:

queryFactory.update (gebruiker) .where (user.login.eq ("Ash")) .set (user.login, "Ash2") .set (user.disabled, true) .execute ();

We kunnen er een aantal hebben .set () clausules die we willen voor verschillende velden. De .waar() clausule is niet nodig, dus we kunnen alle records in één keer bijwerken.

Om de records te verwijderen die aan een bepaalde voorwaarde voldoen, kunnen we een vergelijkbare syntaxis gebruiken:

queryFactory.delete (gebruiker) .where (user.login.eq ("David")) .execute ();

De .waar() clausule is ook niet nodig, maar wees voorzichtig, want het weglaten van de .waar() clausule resulteert in het verwijderen van alle entiteiten van een bepaald type.

U vraagt ​​zich misschien af ​​waarom JPAQueryFactory heeft niet de .insert () methode. Dit is een beperking van de JPA Query-interface. Het onderliggende javax.persistence.Query.executeUpdate () methode is in staat update en delete uit te voeren, maar geen insert-instructies. Om gegevens in te voegen, moet u de entiteiten gewoon behouden met EntityManager.

Als u nog steeds gebruik wilt maken van een vergelijkbare Querydsl-syntaxis voor het invoegen van gegevens, moet u SQLQueryFactory klasse die zich in de querydsl-sql-bibliotheek bevindt.

5. Conclusie

In dit artikel hebben we een krachtige en typeveilige API ontdekt voor persistente objectmanipulatie die wordt geleverd door Querydsl.

We hebben geleerd om Querydsl aan projecten toe te voegen en hebben de gegenereerde Q-typen verkend. We hebben ook enkele typische gebruiksscenario's behandeld en genoten van hun beknoptheid en leesbaarheid.

Alle broncode voor de voorbeelden is te vinden in de github-repository.

Ten slotte zijn er natuurlijk nog veel meer functies die Querydsl biedt, waaronder het werken met onbewerkte SQL, niet-persistente verzamelingen, NoSQL-databases en zoeken in volledige tekst - en we zullen enkele hiervan in toekomstige artikelen onderzoeken.