Einführung in Querydsl

1. Einleitung

Dies ist ein Einführungsartikel, der Sie mit der leistungsstarken Querydsl-API für Datenpersistenz vertraut macht.

Ziel ist es, Ihnen die praktischen Tools zum Hinzufügen von Querydsl zu Ihrem Projekt zu vermitteln, die Struktur und den Zweck der generierten Klassen zu verstehen und ein grundlegendes Verständnis für das Schreiben typsicherer Datenbankabfragen für die meisten gängigen Szenarien zu erlangen.

2. Der Zweck von Querydsl

Objektrelationale Mapping-Frameworks bilden den Kern von Enterprise Java. Diese kompensieren die Nichtübereinstimmung zwischen objektorientiertem Ansatz und relationalem Datenbankmodell. Sie ermöglichen Entwicklern auch, saubereren und präziseren Persistenzcode und Domänenlogik zu schreiben.

Eine der schwierigsten Entwurfsoptionen für ein ORM-Framework ist jedoch die API zum Erstellen korrekter und typsicherer Abfragen.

Eines der am häufigsten verwendeten Java ORM-Frameworks, Hibernate (und auch der eng verwandte JPA-Standard), schlägt eine stringbasierte Abfragesprache HQL (JPQL) vor, die SQL sehr ähnlich ist. Die offensichtlichen Nachteile dieses Ansatzes sind die mangelnde Typensicherheit und das Fehlen einer statischen Abfrageprüfung. In komplexeren Fällen (z. B. wenn die Abfrage abhängig von bestimmten Bedingungen zur Laufzeit erstellt werden muss) umfasst das Erstellen einer HQL-Abfrage in der Regel die Verkettung von Zeichenfolgen, die normalerweise sehr unsicher und fehleranfällig ist.

Der JPA 2.0-Standard brachte eine Verbesserung in Form der Criteria Query API - einer neuen und typsicheren Methode zum Erstellen von Abfragen, bei der Metamodellklassen genutzt wurden, die während der Vorverarbeitung von Anmerkungen generiert wurden. Leider war die Criteria Query API in ihrem Wesen bahnbrechend und sehr ausführlich und praktisch unlesbar. Hier ist ein Beispiel aus dem Jakarta EE-Tutorial zum Generieren einer Abfrage, die so einfach wie SELECT p FROM Pet p ist :

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

Kein Wunder, dass bald eine adäquatere Querydsl-Bibliothek entstand, die auf der gleichen Idee von generierten Metadatenklassen basiert und dennoch mit einer fließenden und lesbaren API implementiert wurde.

3. Querydsl-Klassengenerierung

Beginnen wir mit der Generierung und Erforschung der magischen Metaklassen, die für die fließende API von Querydsl verantwortlich sind.

3.1. Hinzufügen von Querydsl zu Maven Build

Das Einfügen von Querydsl in Ihr Projekt ist so einfach wie das Hinzufügen mehrerer Abhängigkeiten zu Ihrer Build-Datei und das Konfigurieren eines Plugins für die Verarbeitung von JPA-Anmerkungen. Beginnen wir mit den Abhängigkeiten. Die Version von Querydsl-Bibliotheken sollte in eine separate Eigenschaft innerhalb von extrahiert werden Abschnitt wie folgt (für die neueste Version der Querydsl-Bibliotheken überprüfen Sie das Maven Central-Repository):

 4.1.3 

Fügen Sie als Nächstes die folgenden Abhängigkeiten zu hinzu Abschnitt Ihrer pom.xml- Datei:

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

Die Querydsl-Apt- Abhängigkeit ist ein Annotation Processing Tool (APT) - Implementierung der entsprechenden Java-API, mit der Annotationen in Quelldateien verarbeitet werden können, bevor sie zur Kompilierungsphase übergehen. Dieses Tool generiert die sogenannten Q-Typen - Klassen, die sich direkt auf die Entitätsklassen Ihrer Anwendung beziehen, denen jedoch der Buchstabe Q vorangestellt ist. Wenn Sie beispielsweise eine Benutzerklasse in Ihrer Anwendung mit der Annotation @Entity gekennzeichnet haben , wird die Der generierte Q-Typ befindet sich in einer QUser.java- Quelldatei.

Der bereitgestellte Umfang der Querydsl-Apt- Abhängigkeit bedeutet, dass diese JAR nur zur Erstellungszeit verfügbar gemacht werden sollte, aber nicht im Anwendungsartefakt enthalten ist.

Die querydsl-jpa-Bibliothek ist die Querydsl-Bibliothek selbst, die zusammen mit einer JPA-Anwendung verwendet werden kann.

Um das Annotation Processing Plugin zu konfigurieren, das querydsl-apt nutzt , fügen Sie Ihrem pom die folgende Plugin-Konfiguration hinzu - innerhalb des Element:

 com.mysema.maven apt-maven-plugin 1.1.3    process   target/generated-sources/java com.querydsl.apt.jpa.JPAAnnotationProcessor    

Dieses Plugin stellt sicher, dass die Q-Typen während des Prozessziels des Maven-Builds generiert werden. Die Konfigurationseigenschaft outputDirectory verweist auf das Verzeichnis, in dem die Quelldateien vom Typ Q generiert werden. Der Wert dieser Eigenschaft wird später nützlich sein, wenn Sie die Q-Dateien erkunden.

Sie sollten dieses Verzeichnis auch zu den Quellordnern des Projekts hinzufügen, wenn Ihre IDE dies nicht automatisch ausführt. Informationen dazu finden Sie in der Dokumentation Ihrer bevorzugten IDE.

Für diesen Artikel verwenden wir ein einfaches JPA-Modell eines Blog-Dienstes, das aus Benutzern und ihren BlogPosts mit einer Eins-zu-Viele-Beziehung zwischen ihnen besteht:

@Entity public class User { @Id @GeneratedValue private Long id; private String login; private Boolean disabled; @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "user") private Set blogPosts = new HashSet(0); // getters and setters } @Entity public class BlogPost { @Id @GeneratedValue private Long id; private String title; private String body; @ManyToOne private User user; // getters and setters }

Um Q-Typen für Ihr Modell zu generieren, führen Sie einfach Folgendes aus:

mvn compile

3.2. Generierte Klassen erkunden

Nun gehen Sie in das Verzeichnis , in der angegebenen ausgabe Eigenschaft von apt-maven-Plugin ( Soll / erzeugt-Quellen / java in unserem Beispiel). Sie sehen eine Paket- und Klassenstruktur, die Ihr Domänenmodell direkt widerspiegelt , außer dass alle Klassen mit dem Buchstaben Q beginnen ( in unserem Fall QUser und QBlogPost ).

Öffnen Sie die Datei QUser.java . Dies ist Ihr Einstiegspunkt zum Erstellen aller Abfragen, bei denen User als Stammentität vorhanden ist. Als erstes werden Sie die Annotation @Generated bemerken, was bedeutet, dass diese Datei automatisch generiert wurde und nicht manuell bearbeitet werden sollte. Wenn Sie eine Ihrer Domänenmodellklassen ändern, müssen Sie mvn compile erneut ausführen , um alle entsprechenden Q-Typen neu zu generieren .

Neben mehreren in dieser Datei vorhandenen QUser- Konstruktoren sollten Sie auch eine öffentliche statische Endinstanz der QUser- Klasse beachten :

public static final QUser user = new QUser("user");

Dies ist die Instanz, die Sie in den meisten Ihrer Querydsl-Abfragen für diese Entität verwenden können, es sei denn, Sie müssen komplexere Abfragen schreiben, z. B. mehrere verschiedene Instanzen einer Tabelle in einer einzigen Abfrage zusammenfügen.

Das Letzte, was beachtet werden sollte, ist, dass es für jedes Feld der Entitätsklasse ein entsprechendes * Pfadfeld im Q-Typ gibt, wie NumberPath-ID , StringPath-Anmeldung und SetPath-BlogPosts in der QUser- Klasse (beachten Sie, dass der Name des Felds entsprechend Set ist pluralisiert). Diese Felder werden als Teile der fließenden Abfrage-API verwendet, auf die wir später stoßen werden.

4. Abfragen mit Querydsl

4.1. Einfaches Abfragen und Filtern

To build a query, first we’ll need an instance of a JPAQueryFactory, which is a preferred way of starting the building process. The only thing that JPAQueryFactory needs is an EntityManager, which should already be available in your JPA application via EntityManagerFactory.createEntityManager() call or @PersistenceContext injection.

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

Now let’s create our first query:

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

Notice we’ve defined a local variable QUser user and initialized it with QUser.user static instance. This is done purely for brevity, alternatively you may import the static QUser.user field.

The selectFrom method of the JPAQueryFactory starts building a query. We pass it the QUser instance and continue building the conditional clause of the query with the .where() method. The user.login is a reference to a StringPath field of the QUser class that we’ve seen before. The StringPath object also has the .eq() method that allows to fluently continue building the query by specifying the field equality condition.

Finally, to fetch the value from the database into persistence context, we end the building chain with the call to the fetchOne() method. This method returns null if the object can’t be found, but throws a NonUniqueResultException if there are multiple entities satisfying the .where() condition.

4.2. Ordering and Grouping

Now let’s fetch all users in a list, sorted by their login in ascension order.

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

This syntax is possible because the *Path classes have the .asc() and .desc() methods. You can also specify several arguments for the .orderBy() method to sort by multiple fields.

Now let’s try something more difficult. Suppose we need to group all posts by title and count duplicating titles. This is done with the .groupBy() clause. We’ll also want to order the titles by resulting occurrence count.

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

We selected the blog post title and count of duplicates, grouping by title and then ordering by aggregated count. Notice we first created an alias for the count() field in the .select() clause, because we needed to reference it in the .orderBy() clause.

4.3. Complex Queries With Joins and Subqueries

Let’s find all users that wrote a post titled “Hello World!” For such query we could use an inner join. Notice we’ve created an alias blogPost for the joined table to reference it in the .on() clause:

QBlogPost blogPost = QBlogPost.blogPost; List users = queryFactory.selectFrom(user) .innerJoin(user.blogPosts, blogPost) .on(blogPost.title.eq("Hello World!")) .fetch();

Now let’s try to achieve the same with subquery:

List users = queryFactory.selectFrom(user) .where(user.id.in( JPAExpressions.select(blogPost.user.id) .from(blogPost) .where(blogPost.title.eq("Hello World!")))) .fetch();

As we can see, subqueries are very similar to queries, and they are also quite readable, but they start with JPAExpressions factory methods. To connect subqueries with the main query, as always, we reference the aliases defined and used earlier.

4.4. Modifying Data

JPAQueryFactory allows not only constructing queries, but also modifying and deleting records. Let’s change the user's login and disable the account:

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

We can have any number of .set() clauses we want for different fields. The .where() clause is not necessary, so we can update all the records at once.

To delete the records matching a certain condition, we can use a similar syntax:

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

The .where() clause is also not necessary, but be careful, because omitting the .where() clause results in deleting all of the entities of a certain type.

You may wonder, why JPAQueryFactory doesn’t have the .insert() method. This is a limitation of JPA Query interface. The underlying javax.persistence.Query.executeUpdate() method is capable of executing update and delete but not insert statements. To insert data, you should simply persist the entities with EntityManager.

If you still want to take advantage of a similar Querydsl syntax for inserting data, you should use SQLQueryFactory class that resides in the querydsl-sql library.

5. Conclusion

In this article we’ve discovered a powerful and type-safe API for persistent object manipulation that is provided by Querydsl.

Wir haben gelernt, Querydsl zum Projekt hinzuzufügen, und die generierten Q-Typen untersucht. Wir haben auch einige typische Anwendungsfälle behandelt und ihre Prägnanz und Lesbarkeit genossen.

Der gesamte Quellcode für die Beispiele befindet sich im Github-Repository.

Schließlich bietet Querydsl natürlich noch viele weitere Funktionen, darunter das Arbeiten mit unformatiertem SQL, nicht persistenten Sammlungen, NoSQL-Datenbanken und die Volltextsuche - und einige davon werden wir in zukünftigen Artikeln untersuchen.