Einführung in Querydsl

Einführung in Querydsl

1. Einführung

Dies ist ein Einführungsartikel, der Sie mit der leistungsstarkenQuerydsl-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 dafür zu erlangen, wie typsichere Datenbankabfragen für die häufigsten Szenarien geschrieben werden.

2. Der Zweck von Querydsl

Objektrelationale Mapping-Frameworks bilden den Kern von Enterprise Java. Diese kompensieren die Diskrepanz 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 SQL sehr ähnliche stringbasierte Abfragesprache HQL (JPQL) vor. 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) werden beim Erstellen einer HQL-Abfrage normalerweise Zeichenfolgen verkettet, was normalerweise sehr unsicher und fehleranfällig ist.

Der JPA 2.0-Standard brachte eine Verbesserung in Form vonCriteria Query API - eine neue und typsichere 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 bahnbrechend und sehr ausführlich und praktisch unlesbar. Hier ist ein Beispiel aus dem Java EE-Tutorial zum Generieren einer Abfrage, die so einfach wieSELECT 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äquatereQuerydsl-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 die flüssige API von Querydsl ausmachen.

3.1. Hinzufügen von Querydsl zu Maven Build

Das Einbinden 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 wie folgt in eine separate Eigenschaft im Abschnitt<project><properties> extrahiert werden (für die neueste Version von Querydsl-Bibliotheken überprüfen Sie das Repository vonMaven Central):


    4.1.3

Fügen Sie als Nächstes die folgenden Abhängigkeiten zum Abschnitt<project><dependencies> Ihrerpom.xml-Datei hinzu:



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

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

Die Abhängigkeit vonquerydsl-aptist 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-Types - Klassen, die sich direkt auf die Entitätsklassen Ihrer Anwendung beziehen, denen jedoch der Buchstabe Q vorangestellt ist. Wenn Sie beispielsweise eineUser-Klasse haben, die mit der Annotation@Entity in Ihrer Anwendung markiert ist, befindet sich der generierte Q-Typ in einerQUser.java-Quelldatei.

Der Umfang vonprovidedfür die Abhängigkeit vonquerydsl-aptbedeutet, dass diese JAR nur zur Erstellungszeit verfügbar gemacht werden sollte, aber nicht im Anwendungsartefakt enthalten ist.

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

Fügen Sie Ihrem POM die folgende Plugin-Konfiguration hinzu, um das Plugin für die Annotationsverarbeitung zu konfigurieren, dasquerydsl-apt nutzt: innerhalb des<project><build><plugins>-Elements:


    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 Maven-Build-Prozesses generiert werden. Die KonfigurationseigenschaftoutputDirectoryzeigt 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 dies von Ihrer IDE nicht automatisch ausgeführt wird. Informationen dazu finden Sie in der Dokumentation Ihrer bevorzugten IDE.

Für diesen Artikel verwenden wir ein einfaches JPA-Modell eines Blog-Dienstes, das ausUsers und derenBlogPosts 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

}

Führen Sie einfach Folgendes aus, um Q-Types für Ihr Modell zu generieren:

mvn compile

3.2. Generierte Klassen erkunden

Gehen Sie nun zu dem Verzeichnis, das in der EigenschaftoutputDirectorydes apt-maven-Plugins angegeben ist (in unserem Beispieltarget/generated-sources/java). Sie sehen eine Paket- und Klassenstruktur, die Ihr Domänenmodell direkt widerspiegelt, außer dass alle Klassen mit dem Buchstaben Q (in unserem FallQUser undQBlogPost beginnen).

Öffnen Sie die DateiQUser.java. Dies ist Ihr Einstiegspunkt zum Erstellen aller Abfragen mitUser als Stammentität. Das erste, was Sie bemerken werden, ist die Anmerkung von@Generated. Dies bedeutet, dass diese Datei automatisch generiert wurde und nicht manuell bearbeitet werden sollte. Wenn Sie eine Ihrer Domänenmodellklassen ändern, müssen Siemvn compile erneut ausführen, um alle entsprechenden Q-Typen neu zu generieren.

Abgesehen von mehrerenQUser Konstruktoren, die in dieser Datei vorhanden sind, sollten Sie auch eine öffentliche statische Endinstanz derQUser Klasse beachten:

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

Dies ist die Instanz, die Sie in den meisten 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 verknüpfen.

Das Letzte, was beachtet werden sollte, ist, dass es für jedes Feld der Entitätsklasse ein entsprechendes*Path-Feld im Q-Typ gibt, wieNumberPath id,StringPath login undSetPath blogPosts in der KlasseQUser (beachten Sie, dass der Name des Feldes, dasSet entspricht, pluralisiert ist). Diese Felder werden als Teile der API für fließende Abfragen verwendet, auf die wir später stoßen werden.

4. Abfragen mit Querydsl

4.1. Einfaches Abfragen und Filtern

Um eine Abfrage zu erstellen, benötigen wir zunächst eine Instanz vonJPAQueryFactory. Dies ist eine bevorzugte Methode zum Starten des Erstellungsprozesses. Das einzige, wasJPAQueryFactory benötigt, istEntityManager, das in Ihrer JPA-Anwendung bereits überEntityManagerFactory.createEntityManager() Aufruf oder@PersistenceContext Injektion verfügbar sein sollte.

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

Jetzt erstellen wir unsere erste Abfrage:

QUser user = QUser.user;

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

Beachten Sie, dass wir den Benutzer einer lokalen VariablenQUserdefiniert und mit der statischen InstanzQUser.userinitialisiert haben. Dies geschieht nur der Kürze halber. Alternativ können Sie das statische FeldQUser.userimportieren.

Die MethodeselectFrom derJPAQueryFactory beginnt mit der Erstellung einer Abfrage. Wir übergeben ihm die InstanzQUserund bauen die Bedingungsklausel der Abfrage mit der Methode.where()weiter auf. Dasuser.login ist eine Referenz auf einStringPath-Feld derQUser-Klasse, die wir zuvor gesehen haben. DasStringPath-Objekt verfügt auch über die.eq()-Methode, mit der die Abfrage durch Angabe der Feldgleichheitsbedingung flüssig fortgesetzt werden kann.

Um den Wert aus der Datenbank in den Persistenzkontext zu bringen, beenden wir die Gebäudekette mit dem Aufruf der MethodefetchOne(). Diese Methode gibtnull zurück, wenn das Objekt nicht gefunden werden kann, löst jedochNonUniqueResultException aus, wenn mehrere Entitäten die Bedingung.where()erfüllen.

4.2. Bestellen und Gruppieren

Rufen wir nun alle Benutzer in einer Liste ab, sortiert nach ihrer Anmeldung in aufsteigender Reihenfolge.

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

Diese Syntax ist möglich, da die Klassen*Path die Methoden.asc() und.desc()haben. Sie können auch mehrere Argumente für die Methode.orderBy()angeben, um nach mehreren Feldern zu sortieren.

Versuchen wir jetzt etwas Schwierigeres. Angenommen, wir müssen alle Beiträge nach Titel gruppieren und doppelte Titel zählen. Dies erfolgt mit der Klausel.groupBy(). Wir möchten die Titel auch nach der Anzahl der Vorkommen sortieren.

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();

Wir haben den Titel des Blogposts und die Anzahl der Duplikate ausgewählt, nach Titel gruppiert und dann nach aggregierter Anzahl sortiert. Beachten Sie, dass wir zuerst einen Alias ​​für das Feldcount() in der Klausel.select() erstellt haben, da wir ihn in der Klausel.orderBy() referenzieren mussten.

4.3. Komplexe Abfragen mit Verknüpfungen und Unterabfragen

Lassen Sie uns alle Benutzer finden, die einen Beitrag mit dem Titel "Hallo Welt!" Geschrieben haben. Für eine solche Abfrage können wir einen inneren Join verwenden. Beachten Sie, dass wir einen AliasblogPost für die verknüpfte Tabelle erstellt haben, um darauf in der.on()-Klausel zu verweisen:

QBlogPost blogPost = QBlogPost.blogPost;

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

Versuchen wir nun, dasselbe mit der Unterabfrage zu erreichen:

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

Wie wir sehen können, sind Unterabfragen Abfragen sehr ähnlich und auch gut lesbar, beginnen jedoch mit den Factory-Methoden vonJPAExpressions. Um Unterabfragen mit der Hauptabfrage zu verbinden, verweisen wir wie immer auf die zuvor definierten und verwendeten Aliase.

4.4. Daten ändern

JPAQueryFactory ermöglicht nicht nur das Erstellen von Abfragen, sondern auch das Ändern und Löschen von Datensätzen. Ändern Sie das Login des Benutzers und deaktivieren Sie das Konto:

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

Wir können beliebig viele.set()-Klauseln für verschiedene Felder haben. Die.where()-Klausel ist nicht erforderlich, daher können wir alle Datensätze gleichzeitig aktualisieren.

Um die Datensätze zu löschen, die einer bestimmten Bedingung entsprechen, können wir eine ähnliche Syntax verwenden:

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

Die.where()-Klausel ist ebenfalls nicht erforderlich, aber seien Sie vorsichtig, da das Weglassen der.where()-Klausel zum Löschen aller Entitäten eines bestimmten Typs führt.

Sie fragen sich vielleicht, warumJPAQueryFactorynicht über die Methode.insert()verfügt. Dies ist eine Einschränkung der JPA-Abfrageoberfläche. Die zugrunde liegendejavax.persistence.Query.executeUpdate()-Methode kann Aktualisierungen ausführen und löschen, aber keine Anweisungen einfügen. Um Daten einzufügen, sollten Sie die Entitäten einfach mit EntityManager beibehalten.

Wenn Sie dennoch eine ähnliche Querydsl-Syntax zum Einfügen von Daten verwenden möchten, sollten Sie die KlasseSQLQueryFactoryverwenden, die sich in der Querydsl-SQL-Bibliothek befindet.

5. Fazit

In diesem Artikel haben wir eine leistungsstarke und typsichere API für die permanente Objektmanipulation entdeckt, die von Querydsl bereitgestellt wird.

Wir haben gelernt, Querydsl zum Projekt hinzuzufügen und die generierten Q-Typen zu untersuchen. Wir haben auch einige typische Anwendungsfälle behandelt und uns über deren Prägnanz und Lesbarkeit gefreut.

Der gesamte Quellcode für die Beispiele befindet sich ingithub 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. Einige davon werden wir in zukünftigen Artikeln untersuchen.