Introduction à Querydsl

1. Introduction

Ceci est un article d'introduction pour vous familiariser avec la puissante API Querydsl pour la persistance des données.

L'objectif ici est de vous donner les outils pratiques pour ajouter Querydsl dans votre projet, comprendre la structure et le but des classes générées et obtenir une compréhension de base de la façon d'écrire des requêtes de base de données de type sécurisé pour les scénarios les plus courants.

2. L'objectif de Querydsl

Les frameworks de mappage objet-relationnel sont au cœur d'Enterprise Java. Ceux-ci compensent le décalage entre l'approche orientée objet et le modèle de base de données relationnelle. Ils permettent également aux développeurs d'écrire un code de persistance et une logique de domaine plus propres et plus concis.

Cependant, l'un des choix de conception les plus difficiles pour un framework ORM est l'API pour créer des requêtes correctes et de type sécurisé.

L'un des frameworks ORM Java les plus utilisés, Hibernate (et également le standard JPA étroitement lié), propose un langage de requête basé sur des chaînes HQL (JPQL) très similaire à SQL. Les inconvénients évidents de cette approche sont le manque de sécurité de type et l'absence de vérification statique des requêtes. De plus, dans des cas plus complexes (par exemple, lorsque la requête doit être construite au moment de l'exécution en fonction de certaines conditions), la création d'une requête HQL implique généralement la concaténation de chaînes qui est généralement très dangereuse et sujette aux erreurs.

La norme JPA 2.0 a apporté une amélioration sous la forme de l'API Criteria Query - une nouvelle méthode sécurisée de création de requêtes qui tirait parti des classes de métamodèles générées lors du prétraitement des annotations. Malheureusement, étant révolutionnaire dans son essence, l'API Criteria Query s'est avérée très verbeuse et pratiquement illisible. Voici un exemple du tutoriel Jakarta EE pour générer une requête aussi simple que SELECT p FROM Pet p :

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

Pas étonnant qu'une bibliothèque Querydsl plus adéquate ait rapidement vu le jour, basée sur la même idée de classes de métadonnées générées, mais implémentée avec une API fluide et lisible.

3. Génération de classe Querydsl

Commençons par générer et explorer les métaclasses magiques qui représentent l'API fluide de Querydsl.

3.1. Ajout de Querydsl à Maven Build

L'inclusion de Querydsl dans votre projet est aussi simple que l'ajout de plusieurs dépendances à votre fichier de construction et la configuration d'un plugin pour le traitement des annotations JPA. Commençons par les dépendances. La version des bibliothèques Querydsl doit être extraite dans une propriété distincte dans le section, comme suit (pour la dernière version des bibliothèques Querydsl, consultez le référentiel Maven Central):

 4.1.3 

Ensuite, ajoutez les dépendances suivantes au section de votre fichier pom.xml :

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

La dépendance querydsl-apt est un outil de traitement des annotations (APT) - implémentation de l'API Java correspondante qui permet le traitement des annotations dans les fichiers source avant qu'elles ne passent à l'étape de compilation. Cet outil génère les soi-disant Q-types - classes qui se rapportent directement aux classes d'entités de votre application, mais sont précédées de la lettre Q. Par exemple, si vous avez une classe User marquée avec l' annotation @Entity dans votre application, alors le le type Q généré résidera dans un fichier source QUser.java .

La portée fournie de la dépendance querydsl-apt signifie que ce fichier jar doit être rendu disponible uniquement au moment de la construction, mais pas inclus dans l'artefact d'application.

La bibliothèque querydsl-jpa est le Querydsl lui-même, conçu pour être utilisé avec une application JPA.

Pour configurer le plugin de traitement des annotations qui tire parti de querydsl-apt , ajoutez la configuration de plugin suivante à votre pom - dans le élément:

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

Ce plugin s'assure que les Q-types sont générés pendant l'objectif de processus de la construction de Maven. La propriété de configuration outputDirectory pointe vers le répertoire dans lequel les fichiers source de type Q seront générés. La valeur de cette propriété sera utile plus tard, lorsque vous explorerez les fichiers Q.

Vous devez également ajouter ce répertoire aux dossiers source du projet, si votre IDE ne le fait pas automatiquement - consultez la documentation de votre IDE préféré pour savoir comment procéder.

Pour cet article, nous utiliserons un modèle JPA simple de service de blog, composé d' utilisateurs et de leurs BlogPosts avec une relation un-à-plusieurs entre eux:

@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 }

Pour générer des Q-types pour votre modèle, exécutez simplement:

mvn compile

3.2. Explorer les classes générées

Allez maintenant dans le répertoire spécifié dans la propriété outputDirectory d'apt-maven-plugin ( target / generated-sources / java dans notre exemple). Vous verrez un package et une structure de classe qui reflètent directement votre modèle de domaine, sauf que toutes les classes commencent par la lettre Q ( QUser et QBlogPost dans notre cas).

Ouvrez le fichier QUser.java . Il s'agit de votre point d'entrée pour créer toutes les requêtes qui ont User comme entité racine. La première chose que vous remarquerez est l' annotation @Generated qui signifie que ce fichier a été généré automatiquement et ne doit pas être édité manuellement. Si vous modifiez l'une de vos classes de modèle de domaine, vous devrez réexécuter mvn compile pour régénérer tous les types Q correspondants.

En plus de plusieurs constructeurs QUser présents dans ce fichier, vous devez également prendre note d'une instance finale statique publique de la classe QUser :

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

Il s'agit de l'instance que vous pouvez utiliser dans la plupart de vos requêtes Querydsl vers cette entité, sauf lorsque vous devez écrire des requêtes plus complexes, comme joindre plusieurs instances différentes d'une table dans une seule requête.

La dernière chose à noter est que pour chaque champ de la classe d'entité, il existe un champ * Path correspondant dans le type Q, comme NumberPath id , StringPath login et SetPath blogPosts dans la classe QUser (notez que le nom du champ correspondant à Set est pluralisé). Ces champs sont utilisés dans le cadre de l'API de requête fluide que nous rencontrerons plus tard.

4. Interrogation avec Querydsl

4.1. Interrogation et filtrage simples

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.

Nous avons appris à ajouter Querydsl au projet et exploré les Q-types générés. Nous avons également couvert certains cas d'utilisation typiques et apprécié leur concision et leur lisibilité.

Tout le code source des exemples se trouve dans le référentiel github.

Enfin, Querydsl propose bien sûr de nombreuses autres fonctionnalités, notamment le travail avec du SQL brut, des collections non persistantes, des bases de données NoSQL et la recherche en texte intégral - et nous en explorerons certaines dans les prochains articles.