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. Requêtes : les langages JPQL et HQL
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

Requêtes : les langages JPQL et HQL

Introduction

Java Persistence Query Language (JPQL) et Hibernate Query Language (HQL) sont deux langages de requêtes focalisés sur la manipulation d’objets et similaires au langage SQL.

Du fait qu’Hibernate était une référence lors de l’établissement de la norme JPA, JPQL est fortement inspiré par HQL.

Le principe de ces langages est de manipuler les propriétés des objets dans les requêtes et ainsi ne pas dépendre des noms de tables/colonnes.

À savoir : une requête JPQL est toujours une requête HQL valide, cependant l’inverse n’est pas vrai.

Généralités

Une requête JPQL, comme une requête HQL ou encore SQL, peut être séparée en cinq parties : le type d’opération, le périmère de la requête, les restrictions, le regroupement et le tri.

  • Le type d’opération correspond au type de requête : INSERT, SELECT, UPDATE, DELETE.

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

  • Les restrictions correspondent à la clause WHERE.

  • Le regroupement correspond aux clauses GROUP BY et HAVING.

  • Le tri correspond à la clause ORDER BY.

De ce fait, il n’y a pas de différence fonctionnelle entre une requête orientée objet et une requête classique SQL.

Types de requêtes

Il est possible de faire en JPQL et HQL des SELECT, UPDATE et DELETE. HQL permet en plus de faire des INSERT.

Lors de modifications importantes de la base de données, il est fortement recommandé de faire attention car il est possible que le cache ne soit plus synchronisé avec la base de données.

La prudence devrait être de mise lors de l’exécution d’opérations de mise à jour majeure ou de suppression, car elles peuvent entraîner des incohérences entre la base de données et les entités dans le contexte de persistance actif. En général, les mises à jour majeures et les opérations de suppression doivent être effectuées dans une transaction dans un nouveau contexte de persistance ou avant de lire ou d’accéder à des entités dont l’état pourrait être touché par de telles opérations. Spécification JPA 2.1 - 4.10.

1. SELECT

Une requête SELECT se compose de :

  • Clause SELECT

  • Clause FROM

  • Clause WHERE

  • Clause GROUP BY

  • Clause HAVING

  • Clause ORDER BY

Les clauses SELECT et FROM sont obligatoires en JPQL. Seule la clause FROM est obligatoire en HQL, mais par convention, il est recommandé d’écrire également le SELECT.

Une requête de sélection est exécutée depuis javax.persistence.Query...

La clause SELECT

La clause SELECT sert à définir le type de retour de l’exécution de la requête. Comme en SQL, il est possible de retourner un tuple en entier ou une partie uniquement. La différence avec le SQL c’est que l’opération s’effectue sur une entité et non un tuple.

Pour sélectionner toutes les personnes :


SELECT p FROM Personne p
 

Retourne List<Personne>.

Pour sélectionner tous les noms :


SELECT p.nom FROM Personne p 
 

Retourne List<String>.

Pour sélectionner tous les noms et prénoms :


SELECT p.nom , p.prenom FROM Personne p
 

Retourne List<Object[]>.

Il est possible de retourner différentes expressions (voir la section Les expressions de ce chapitre).

Il existe certaines expressions, uniquement disponibles pour la clause SELECT, appelées constructor expression en JPQL et dynamic instantiation en HQL.


SELECT new PersonneSimple(p.nom, p.prenom) 
FROM Personne 
 

Retourne List<PersonneSimple>.

Au lieu d’avoir un tableau d’objets, les valeurs sont encapsulées dans un objet défini. La classe référence doit être entièrement qualifiée et son constructeur doit être identique.


public class PersonneSimple { 
    private String nom; 
    private String prenom; 
  
    public PersonneSimple(String nom...

La clause FROM

La clause FROM est responsable du périmètre de la requête, c’est-à-dire des entités auxquelles les autres clauses auront accès.

Elle est aussi responsable des variables d’identification pour la requête.

1. Variables d’identification

Les variables d’identification sont plus communément appelées « alias ». Ce sont des références aux objets afin de pouvoir les manipuler plus facilement.

Selon JPQL, les variables d’identification ne sont pas sensibles à la casse, c’est-à-dire qu’un ORM devrait pouvoir interpréter toute variable peu importe sa casse.

Afin de faciliter la lecture, il est fortement recommandé de garder la même écriture pour tous les alias.

2. Référence à l’entité root

La référence à l’entité root correspond à une référence à une entité mappée depuis l’application.

Pour faire référence à une entité, il suffit de d’écrire le nom de l’entité complet ou non.

Par exemple, pour faire référence à l’entité com.eni.jpa.entity.Personne, il y a trois possibilités :

  • En utilisant la dénomination la plus complète :


SELECT p FROM com.eni.jpa.entity.Personne AS p
 
  • Sans...

La clause WHERE

La clause WHERE permet de filtrer les données impactées par le type d’opération (SELECT, UPDATE, DELETE). Des quatre clauses facultatives, elle est la plus utilisée.

Les alias utilisés dans la clause WHERE doivent forcément être définis dans le périmètre de la requête.

La clause WHERE s’opère sur des booléens qui sont le résultat de différentes opérations (prédicats).


SELECT e  
FROM Entite AS e 
WHERE condition
 

Comme en SQL, il est possible de restreindre sur plusieurs conditions avec les opérations sur les booléens (AND, OR, ()).

Par exemple, la requête suivante permet de sélectionner tous les pays dont le nom commence par ’F’ et dont la monnaie est l’euro, ou ceux dont la langue est le français :


SELECT p 
FROM Pays p 
WHERE (p.nom like 'F%' AND p.monnaie='Euro') 
 OR p.langueList.nom = 'Français'
 

Les expressions

Une expression représente une valeur, que ce soit une donnée littérale (booléenString…), une variable (alias, PATH, paramètre) ou le résultat d’une fonction.

1. Alias

Les alias correspondent aux variables d’identification expliquées à la section La clause FROM - Variables d’identification.

2. PATH

Les expressions de PATH sont expliquées à la section La clause FROM - Les jointures.

3. Paramètres

Afin de pouvoir modifier une requête après sa construction, il est possible de mettre en place des paramètres, JPQL en supporte de deux types :

  • Les paramètres nommés.

  • Les paramètres de position.

Les paramètres nommés s’écrivent de la façon suivante :


:nomDuParamètre
 

Ils peuvent être présents plusieurs fois dans la requête pour être réutilisés, ce qui donne :


String requete = "SELECT p FROM Pays p "+ 
"WHERE p.nom = :nom "+ 
"OR p.langueList.nom = :nom"; 
  
String leNomAChercher = ... 
  
List<Pays> resultat = em.createQuery(requete,Pays.class).
setParameter("nom",leNomAChercher).getResultList();
 

Les paramètres de position s’écrivent de la façon suivante :


?numéro
 

Ils peuvent être présents plusieurs fois dans la requête pour être réutilisés, ce qui donne :


String requete = "SELECT p FROM Pays p "+ 
"WHERE p.nom = ?1 "+ 
"OR p.langueList.nom = ?1"; 
  
String leNomAChercher = ... 
  
List<Pays> resultat =  
em.createQuery(requete,Pays.class).setParameter(1, 
leNomAChercher).getResultList();
 

4. Littéral

Les expressions littérales représentent une constante pour la requête. JPQL supporte différents types de littéraux tels que le NULL, les booléens, les numériques, les strings, les dates, les énum et les types d’entités.

a. Le littéral NULL

Le littéral NULL représente une valeur nulle, et comme JPQL n’est pas sensible à la casse, il peut s’écrire NULL, null, nUll… Il est de bonne pratique de garder la même casse tout au long du projet.

Afin de tester...

Les clauses GROUP BY et HAVING

La clause GROUP BY permet de regrouper les résultats. Le regroupement ne permet plus de sélectionner une entité et uniquement certaines valeurs sont accessibles :

  • Les propriétés du regroupement.

  • Les fonctions d’agrégations (count, sum, avg, min, max).

Le GROUP BY s’effectue, lors de l’exécution de la requête, entre la clause WHERE et la clause SELECT.

Par exemple, la requête suivante permet de sélectionner la langue parlée par les pays du continent Europe ainsi que le nombre de pays concernés.


SELECT l.nom,count(p) 
FROM Pays p JOIN p.langueList l 
WHERE p.continent = 'Europe'  
GROUP BY l.nom
 

Il est possible de filtrer les informations après le GROUP BY via la clause HAVING ; uniquement les valeurs du GROUP BY ou les fonctions d’agrégations sont autorisées.

Par exemple, la requête suivante permet d’additionner par langue, la population des pays du continent Europe parlant cette langue, puis de retourner uniquement les langues étant parlées au minimum par deux pays.


SELECT l.nom, sum(p.population)  
FROM Pays p JOIN p.langueList l 
WHERE p.continent = 'Europe'  
GROUP BY l.nom  
HAVING count(p) > 1
 

La clause ORDER BY

La clause ORDER BY permet de trier les résultats. Toute requête JPQL n’ayant pas de clause ORDER BY produit un résultat non trié.

Lors de l’exécution de la requête, l’ORDER BY s’effectue en dernier, après le SELECT.

Par exemple, la requête suivante sélectionne le nom des pays du continent Europe triés par nom.


SELECT p.nom  
FROM Pays p 
WHERE p.continent = 'Europe'  
ORDER BY p.nom
 

L’ordre de tri peut être soit ascendant (ASC), soit descendant (DESC). Par défaut, le tri est ascendant.


// tri par défaut (ascendant) 
SELECT p.nom FROM Pays p ORDER BY p.nom  
  
//équivalent 
SELECT p.nom FROM Pays p ORDER BY p.nom ASC 
  
// tri descendant 
SELECT p.nom FROM Pays p ORDER BY p.nom DESC
 

Il est possible de trier sur différents critères en les ajoutant les uns à la suite des autres, séparés par une virgule. Chaque critère de tri additionnel sera pris en compte uniquement si l’ordre de tri précédent est identique.

La requête suivante trie les pays par monnaie en ordre décroissant, puis lorsque plusieurs pays ont la même monnaie, le tri s’effectue sur le nom du pays par ordre croissant :


SELECT p.nom, p.monnaie FROM Pays p  
ORDER BY p.monnaie DESC, p.nom
 

Les valeurs prises...

L’UPDATE

Comme vu dans le chapitre Manipulation des données - Modification d’une entité, JPA permet la mise à jour des données via la manipulation d’objets dans le code source du projet. Il est également possible de créer des requêtes JPQL afin de modifier ces données.

Mettre à jour les données directement via une requête JPQL est au moins aussi rapide que récupérer l’objet, le mettre à jour puis le sauvegarder. Par contre, il faut être prudent car mettre à jour des valeurs sans passer par l’EntityManager risque de le désynchroniser avec la base de données (il est possible que l’EntityManager ne soit pas conscient qu’une entité dans son cache a été mise à jour). Une bonne pratique est d’utiliser un autre EntityManager afin de faire les requêtes de mise à jour.

Les opérations de mise à jour ne peuvent être exécutées que lors d’une transaction active. Les changements sont visibles, depuis un autre EntityManager, uniquement lors du commit.

1. Mise à jour de toutes les données

L’UPDATE sans restriction met à jour toutes les données.

Les trois requêtes suivantes, qui sont équivalentes, permettent de mettre le nom des pays en majuscules.


UPDATE Pays SET nom = UPPER(nom) 
UPDATE Pays...

Le DELETE

Comme vu dans le chapitre Manipulation des données - Suppression d’une entité, JPA permet la suppression des données via la manipulation d’objets dans le code source du projet. Il est également possible de créer des requêtes JPQL afin de supprimer ces données.

Supprimer les données directement via une requête JPQL est au moins aussi rapide que de récupérer l’objet puis le supprimer. Par contre, il faut être prudent car effacer des données sans passer par l’EntityManager risque de le désynchroniser avec la base de données (il est possible que l’EntityManager ne soit pas conscient qu’une entité dans son cache a été supprimée). Une bonne pratique est d’utiliser un autre EntityManager afin de faire les requêtes DELETE

Les opérations de suppression ne peuvent être exécutées que lors d’une transaction active. Les changements sont visibles, depuis un autre EntityManager, uniquement lors du commit.

1. Supprimer toutes les données

Le DELETE sans restriction permet de supprimer toutes les données.

Il se compose des mots-clés DELETE FROM suivis du nom de l’entité à supprimer.

Par exemple, les trois requêtes suivantes, qui sont équivalentes, permettent de supprimer les données de l’entité...

L’INSERT

La requête INSERT n’est pas prise en compte par JPQL, mais elle est possible en HQL.

Contrairement à une requête d’insertion SQL, où il est possible d’insérer des données arbitraires, l’INSERT de HQL ne peut être utilisé que pour insérer des données construites depuis une requête de sélection. Son application est donc limitée.

La requête suivante permet de copier les pays de la table Pays vers la table SavePays :


INSERT INTO SavePays(id,nom)  
SELECT id, nom FROM Pays