Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. JPA et Java Hibernate
  3. L'API Criteria
Extrait - JPA et Java Hibernate Apprenez le mapping objet-relationnel (ORM) avec Java
Extraits du livre
JPA et Java Hibernate Apprenez le mapping objet-relationnel (ORM) avec Java Revenir à la page d'achat du livre

L'API Criteria

Introduction

Avec les requêtes basées sur des chaînes de caractères (JPQL, HQL ou SQL), il n’est pas possible de contrôler leur syntaxe avant leur exécution de manière automatique.

Par exemple, une faute de frappe, une mauvaise entité dans une requête JPQL et/ou une mauvaise syntaxe n’empêchent pas la compilation du code source :


Query query = em.createQuery("SELCT e From EntiteInexistante e WHERE AND  
e.champ = 'valeur'");
 

De ce fait, Hibernate a mis en place une API nommée Criteria afin de prendre en compte cette problématique. Cela a aussi permis la construction de requêtes dynamiques via une liste d’interfaces et d’objets. JPA a introduit sa propre API de construction de requêtes lors de sa version 2.0, qui est aussi nommée Criteria, qui est désormais la norme et est donc reprise par chacune de ses implémentations. Ainsi, lors de l’utilisation de l’API Criteria, il est important de s’assurer de la version utilisée : javax.persistence pour JPA (celle qui nous intéresse) et org.hibernate pour l’API d’Hibernate.

Comme la construction d’une requête avec l’API Criteria de JPA se fait avec l’instanciation de différents objets et appels de méthodes, elle est un peu plus complexe et verbeuse que l’écriture avec JPQL.

Par exemple...

L’API Metamodel

L’API Metamodel est donc l’API de JPA qui permet de définir le métamodèle au sein d’une application basée sur JPA.

Le métamodèle est une liste de classes représentant la structure des données de l’unité de persistance, c’est une image des différentes entités utilisées.

Ainsi, chaque classe du métamodèle décrit une entité avec ses propriétés et ses relations avec les autres, sans décrire son lien avec la base de données (ex. : la colonne) ni ses interactions (ex. : le type de cascade).

Ce métamodèle a deux utilités :

  • Le principe de métamodèle existe pour toutes les implémentations. Il leur permet d’accéder de façon générique à la structure des données. JPA ne fait que définir une norme, indépendante de l’accès à ces informations.

  • Il permet l’accès à la structure des données sous la forme d’objets, pour ainsi construire des requêtes avec des types sécurisés, surtout dans le cas du métamodèle statique.

Ainsi, deux types de métamodèles existent :

  • Le métamodèle dynamique, qui, lui, est généré lors de l’exécution du code source (lors du runtime). Il va servir aux différentes implémentations.

  • Le métamodèle statique, qui, lui, est généré dans le code source afin de le rendre accessible au moment du développement.

1. Le métamodèle dynamique

Le métamodèle dynamique est instancié par l’implémentation JPA utilisée, ainsi il permet d’accéder à la structure des données...

Généralités

L’API Criteria permet d’interroger les données du contexte de persistance, donc par extension, la base de données. Ainsi, comme pour une requête JPQL, HQL ou encore SQL, une requête Criteria peut être séparée en cinq parties :

  • Le type d’opération, qui correspond au type de requête : SELECT, UPDATE ou DELETE. L’INSERT n’est pas possible avec l’API Criteria.

  • Le scope ou périmètre de la requête, qui correspond à la clause FROM et les jointures.

  • Les restrictions, qui correspondent à la clause WHERE.

  • Le regroupement, qui correspond aux clauses GROUP BY et HAVING.

  • Le tri, qui correspond à la clause ORDER BY.

De plus, chacune de ces parties peut accepter des expressions telles qu’un attribut, une condition, une opération...

Cette API est articulée autour du CriteriaBuilder. C’est-à-dire que le point de départ de construction de chaque requête Criteria est toujours une instance d’un CriteriaBuilder. Cette instance se récupère depuis un EntityManager ...

Le type d’opération

Le type d’opération est l’action effectuée sur la base de données. Il n’est possible de réaliser que trois types d’opérations avec l’API Criteria qui sont la lecture, la modification et la suppression (SELECT, UPDATE et DELETE). Chacune de ces opérations a ses propres classes qui sont :

  • CriteriaQuery pour le SELECT.

  • CriteriaUpdate pour l’UPDATE.

  • CriteriaDelete pour le DELETE.

La récupération de ces classes se fait depuis le CriteriaBuilder avec leur méthode createXXX()XXX correspond au mot-clé pour la classe voulue. Ce qui implique qu’avant de construire une requête Criteria, il faut savoir quel type d’opération va être effectué.

Ainsi, pour chaque opération voulue, les méthodes sont :

  • CriteriaQuery : createQuery(), createQuery(Class<T> resultClass) et createTupleQuery().

  • CriteriaUpdate : createCriteriaUpdate(Class<T> targetClass).

  • CriteriaDelete : createCriteriaDelete(Class<T> targetClass).

Par exemple, pour récupérer un CriteriaDelete afin de supprimer des entités Personne :


EntityManager em = ...; 
 
//Récupération du CriteriaBuilder 
CriteriaBuilder cb = em.getCriteriaBuilder(); 
 
//Récupération du CriteriaDelete 
CriteriaDelete<Personne> cd = cb.createCriteriaDelete(Personne.class);
 

1. CriteriaQuery

Les CriteriaQuery permettent d’effectuer des requêtes de sélection sur les données et peuvent donc retourner soit un résultat unique, soit une liste de résultats. Ce résultat peut être une entité ou une liste d’expressions telles qu’un attribut, une opération arithmétique, une expression booléenne...

Afin de construire la requête, il y a un certain ordre à respecter :

  • Définir le type de retour (obligatoire)

  • Paramétrer le périmètre (obligatoire)

  • Paramétrer la restriction

  • Paramétrer le regroupement

  • Paramétrer le retour (obligatoire dans certains cas)

  • Paramétrer le tri

  • Exécuter la requête

a. Le type de retour

Ainsi, le retour de la requête va conditionner la façon dont va être créé le CriteriaQuery

Si le retour de la requête...

Le périmètre (FROM)

Le périmètre d’une requête sert à définir les données qui sont impliquées dans la manipulation. Que ce soit les données qui seront retournées, modifiées, supprimées ou utilisées en interne (restriction, regroupement, tri). Avec une requête basée sur une chaîne de caractères (JPQL, SQL...), ce sont les informations qui se trouvent entre le mot-clé FROM et le mot-clé WHERE. C’est-à-dire les tables à utiliser avec leurs jointures éventuelles.

1. Root

Pour l’API Criteria, les entités à utiliser sont des objets Root<X> avec, pour X, l’entité concernée. Ainsi, pour faire référence à une entité dans une requête Criteria, il faut utiliser sa méthode from() avec comme paramètre la classe de l’entité à utiliser.

Par exemple, pour utiliser l’entité Personne dans un CriteriaQuery :


CriteriaQuery cq = ...  
 
//Définition de l'entité Root 
Root<Personne> p = cq.from(Personne.class);
 

Cette définition a deux rôles importants :

  • Le premier est de renseigner la future clause FROM de la requête JPQL qui sera exécutée.

Dans l’exemple ci-dessus, la clause FROM qui en découle est la suivante :


FROM Personne p
 
  • Le second est de retourner un objet qui peut être utilisé afin de continuer à compléter la requête (comme la récupération d’un attribut, faire une jointure, la récupération de l’entité pour le retour...).

2. Multiple Root

Dans certains cas, la référence à une seule entité Root n’est pas suffisante. Avec l’API Criteria, il suffit d’appeler plusieurs fois la méthode from() afin de faire référence à toutes les entités voulues.

Ainsi, l’utilisation de la méthode from() d’un CriteriaQuery ne remplace pas l’ancienne donnée, elles sont ajoutées. Ce principe est différent pour les CriteriaUpdate et CriteriaDelete car, pour eux, il ne peut y avoir qu’une seule entité Root.

Par exemple, pour un CriteriaQuery :


CriteriaQuery...

Les restrictions (WHERE)

Les restrictions permettent de limiter les données impactées par le périmètre de la requête. Elles correspondent à la clause WHERE d’une requête JPQL. Ainsi, pour paramétrer les restrictions d’une requête Criteria, il faut utiliser la méthode where() que ce soit pour un CriteriaQuery, CriteriaUpdate ou CriteriaDelete.

L’appel de la méthode where() peut être fait de cinq manières différentes :

  • Avec une Expression<Boolean>.

  • Avec un Predicate.

  • Avec plusieurs Predicate.

  • Avec un tableau de Predicate (Predicate[]).

  • Sans paramètre.

Il faut savoir qu’un Predicate hérite de Expression<Boolean> et permet donc d’évaluer des conditions. Les différentes possibilités des Predicate sont décrites à la section Les expressions.

De plus, la méthode where() remplace à chaque appel les anciennes conditions. Ainsi, les conditions de restriction doivent être paramétrées en une seule fois.

Par exemple :


Root<PersonneAdresse> pa = cq.from(PersonneAdresse.class); 
 
Predicate condition1 = ...  
Predicate condition2 = ... 
 
cq.where(condition1);  
cq.where(condition2);
 

Donne en JPQL :


WHERE condition2
 

1. Avec Expression<Boolean>

L’appel de la méthode where() avec Expression<Boolean> sert principalement pour faire un test sur un attribut de type Boolean.

Par exemple, pour récupérer la liste des entités PersonneAdresse dont l’attribut principal est à true :


Root<PersonneAdresse> pa = cq.from(PersonneAdresse.class); 
 
//Récupération de l'attribut...

Le regroupement (GROUP BY et HAVING)

Le regroupement permet de faire des opérations d’agrégation sur des données. Il se décompose en deux étapes. La première est la définition des données à regrouper (la clause GROUP BY) et la deuxième est une étape de restriction sur les données regroupées (la clause HAVING). Comme avec JPQL, ce regroupement ne peut être effectué que sur une opération de type sélection, c’est-à-dire avec un objet CriteriaQuery. Ainsi, il faut utiliser les méthodes groupBy() et having(). La méthode groupBy() utilise des Expression<?> et la méthode having() utilise une Expression<Boolean> ou des Predicate (comme la méthode where()).


CriteriaQuery cq =... 
 
 
Expression<?> eGroupBy = ... 
Predicate prHaving = ...  
 
cq.groupBy(eGroupBy); 
cq.having(prHaving);
 

Ce qui va donner en JPQL :


GROUP BY eGroupBy HAVING prHaving
 

Ces deux méthodes fonctionnent comme la méthode where(), c’est-à-dire que l’appel de ces méthodes remplace les anciennes données. Ce qui implique que l’appel de ces méthodes sans paramètre supprime l’ancienne configuration.


CriteriaQuery cq =... 
 
 
Expression<?> eGroupBy1 = ... 
Expression<?> eGroupBy2 = ... 
 
Predicate prHaving = ...  
 
cq.groupBy(eGroupBy1);  
cq.groupBy(eGroupBy2); 
 
cq.having(prHaving);  
cq.having();
 

Ce qui va donner en JPQL :


GROUP BY eGroupBy2 -- Pas de HAVING
 

Comme pour JQL, les Predicate...

Le tri (ORDER BY)

Le tri permet d’organiser les données qui sont sélectionnées. C’est la clause ORDER BY d’une requête. De ce fait, le tri n’est disponible que pour les CriteriaQuery (les requêtes SELECT) en utilisant leur méthode orderBy() et en lui passant en paramètre de 0 à n objets Order sous la forme de suite d’objets (séparés par une virgule), d’une List<Order> ou d’un tableau d’Order


CriteriaQuery cq =... 
 
 
Order tri = ... 
 
 
cq.orderBy(tri);
 

Ce qui va donner en JPQL :


ORDER BY tri
 

Comme pour les méthodes where(), groupBy() et having(), la méthode orderBy() réinitialise à chaque fois les conditions de tri, ce qui implique que la dernière utilisation est celle prise en compte, et si elle est vide, le tri est effacé. 

Ci-dessous, l’état de la clause ORDER BY en fonction de l’appel de la méthode orderBy(...) :


CriteriaQuery cq =... 
 
 
Order tri = ... 
Order tri2 = ... 
 
//JPQL : -- Pas de tri 
 
cq.orderBy(tri);  
//JPQL : ORDER BY tri 
 
cq.orderBy(tri2); 
//JPQL : ORDER BY tri2 
 
 
cq.orderBy(); 
//JPQL : -- Pas de tri
 

1. L’objet Order

L’objet Order de l’API Criteria sert à définir l’ordre de tri. Pour l’instancier...

Les expressions

Avec l’API Criteria, l’objet Expression<?> représente une valeur, que ce soit une donnée littérale (booléen, String...), une variable (Root, Join, paramètre), ou le résultat d’une fonction.

1. PATH

Un objet Path est une expression qui représente un attribut. Il permet en plus de naviguer au sein du modèle. Les objets Root et Join héritent de Path.

Ainsi, cet objet Path va permettre de :

  • Définir l’attribut à retourner dans un SELECT.

  • Définir l’attribut à modifier.

  • Récupérer un de ses attributs (si l’attribut d’origine est une entité).

La navigation dans le modèle s’effectue avec la méthode get() de l’objet Path.

Par exemple, pour récupérer l’attribut nom d’une entité Personne qui est définie en tant que Root :


Root<Personne> p = ...  
 
//Récupération de l'attribut nom 
Path<String> attNom = p.get(Personne_.nom);
 

Grâce à l’autocasting via le modèle, il apparaît donc que la récupération de l’attribut nom de l’entité Personne donne une Expression de type Path<String> ce qui signifie que la donnée contenue dans cet attribut est un String.

Ce qui implique que l’attribut récupéré via la méthode get() est directement prêt à l’emploi et la navigation se fait simplement en appellant les méthodes get() à la suite.

Par exemple, pour récupérer l’attribut numSecu de l’entité PersonneDetail en démarrant d’un Root<Personne> :


Path<String> attNumSecu =  
p.get(Personne_.personneDetail).get(PersonneDetail_.numSecu);
 

2. Paramètres

Avec JPQL, il est possible de mettre en place des paramètres. Comme avec l’API Criteria il n’est pas possible d’écrire directement dans la requête, il faut utiliser l’objet ParameterExpression<?>. Afin de récupérer une expression de type paramètre, il faut utiliser la méthode parameter() de CriteriaBuilder. La méthode parameter() a un premier paramètre obligatoire qui sert à définir la classe du paramètre.

Par exemple, pour définir...

Exemples

1. Écriture minimum

En JPQL, l’écriture minimum d’une requête est :


FROM Entite
 

Par exemple, pour récupérer toutes les entités Personne :


String requete = "FROM Personne"; 
 
List<Personne> lstPers = em.createQuery(requete).getResultList();
 

Ainsi, avec l’API Criteria, comme pour JPQL, l’appel de la méthode select() n’est pas obligatoire s’il n’y a qu’un seul type d’entité retourné.

L’exemple ci-dessus s’écrit avec l’API Criteria :


EntityManager em = ... 
 
//Définition de la requête 
CriteriaBuilder cb = em.getCriteriaBuilder(); 
CriteriaQuery cq = cb.createQuery(); 
Root<Personne> p = cq.from(Personne.class); 
 
//Exécution de la requête 
List<Personne> res = em.createQuery(cq).getResultList();
 

2. Écriture successive des méthodes

Du fait que les méthodes select(), multiselect(), where(), groupBy(), having() et orderBy() retournent le même CriteriaQuery, il est possible d’enchaîner les appels afin de créer la requête sur un minimum de lignes sans déclarer d’autres variables que celles du périmètre (Root et Join). Ainsi, la déclaration ressemble un peu plus à ce qui est habituel de voir en JPQL.

Par exemple, pour exécuter...