11. Le langage HQL#
Voici donc maintenant une présentation des aspects essentiels de HQL. Mon but n’étant pas de ré-écrire la documentation en ligne, et donc d’être exhaustif, le contenu qui suit se concentre sur les principes, avec des exemples illustratifs.
S1: HQL, aspects pratiques#
Supports complémentaires :
Pour tester HQL, je vous recommande d’utiliser, sous Eclipse, le plugin Hibernate Tools. Voici comment l’installer, et quelques instructions d’utilisation.
Sous Eclipse, dans le menu Help, accédez au choix Marketplace (ou Software search and updates, selon les versions).
Lancez une recherche sur Hibernate Tools.
Lancez l’installation, puis relancez Eclipse quand elle est terminée.
Une fois le plugin installé, vous pouvez ouvrir des vues Eclipse. Dans le menu Windows, puis Show view, choisissez l’option Hibernate comme le montre la figure suivante:
Configuration#
Lancez tout d’abord la fenêtre Hibernate Configurations. Vous pouvez créer des configurations
(soit, essentiellement, le contenu d’un fichier hibernate.cfg.xml
). Une configuration
peut être créée à partir d’un projet existant, ce qui revient à utiliser les paramètres Hibernate
déjà définis pour le projet.
Quand vous êtes sur l’onglet Configuration de la nouvelle fenêtre, des boutons sur la droite permettent de créer une nouvelle configuration; pour une configuration existante, le bouton droit donne accès à l’édition de la configuration, au lancement d’un éditeur HQL, etc.
Le plus simple est donc d’indiquer le projet associé à la configuration, et d’utiliser le fichier
de mapping hibernate.cfg.xml
de ce projet. Cela assure que les classes persistantes et
la connexion à la base sont automatiquement détectées. L’outil vous permet également de créer ou modifier
un fichier de configuration avec une interface graphique asez agréable.
Utilisation#
Une fois la configuration créée, vous pouvez lancer un éditeur HQL et y saisir des requêtes. Pour l’éditeur HQL, sélectionnez votre configuration de session, et utilisez soit le bouton droit, soit l’icône « HQL » que vous voyez sur la barre d’outils. Vous pouvez saisir des requêtes HQL dans l’éditeur, et les exécuter avec la flèche verte.
Trois autres vues sont utiles:
Hibernate Dynamic SQL Preview vous montre la/les requête(s) SQL lancée(s) par Hibernate pour évaluer la requête HQL; très utile pour comprendre comment Hibernate matérialise le graphe requis par une requête HQL.
Hibernate Query Result vous donne simplement le résultat de la requête HQL.
Query parameters sert à définir la valeur des paramètres pour les requêtes qui en comportent.
Pour résumer, votre environnement de test HQL devrait ressembler à la figure ci-dessous.
Une dernière astuce: les requêtes HQL renvoient en général des objets, et Hibernate Tools affiche la
référence de ces objets,
ce qui n’est pas très parlant. Pour consulter plus facilement le résultat des requêtes, vous pouvez ajouter une
clause select
(optionnelle en HQL):
select film.titre, film.annee
from Film as film
HQL et java#
L’intégration de HQL à Java se fait par des méthodes de la Session
.
Le code suivant recherche un (ou plusieurs) film(s) par leur titre.
public List<Film> parTitre(String titre)
{
Query q = session.createQuery("from Film f where f.titre= :titre");
q.setString ("titre", titre);
return q.list();
}
La clause select
est optionnelle en HQL, et offre peu d’intérêt
dans le cadre de l’application java car on cherche en général à récupérer
automatiquement des instances des classes mappées, ce qui implique de
récupérer tous les attributs de chaque ligne.
Commme en jdbc, on peut introduire dans la requête des paramètres (ci-dessus, le titre) en les préfixant par « : » (« ? » est également accepté). Hibernate se charge de protéger la syntaxe de la requête, par exemple en ajoutant des barres obliques devant les apostrophes et autres caractères réservés. Voici une requête un peu plus complète, tout en étant formulée plus concisément.
session.createQuery("from Film as film where film.titre like :titre and film.annee < :annee")
.setString ("titre", "%er%")
.setInteger("annee", 2000)
.list();
On applique la technique dite de « chaînage des méthodes », chaque méthode set
renvoyant l’objet-cible, auquel on peut donc appliquer une nouvelle méthode,
et ainsi de suite, pour une syntaxe beaucoup plus concise.
Notez également que l’affectation des paramètres tient compte de leur type:
Hibernate propose des setDate()
, setInteger()
, setTimestamp()
, etc.
On peut également utiliser comme paramètre un objet persistant, comme
le montre l’exemple suivant:
// bergman est une instance de Artiste
Artiste bergman = ...;
session.createQuery("from Film as film where film.realisateur= :mes")
.setEntity ("mes", bergman)
.list();
HQL offre la possibilité de paginer les résultats, une option souvent utile dans un contexte d’application web. L’exemple suivant retourne les lignes 10 à 19 du résultat.
session.createQuery("from Film")
.setFirstResult (10)
.setMaxResults(10)
.list();
La méthode list()
de l’objet query
est la plus générale pour exécuter une
requête et constituer le résultat. Si vous êtes sûr que ce dernier ne contient qu’un
seul objet, vous pouvez utiliser uniqueResult()
.
session.createQuery("from Film where titre='Vertigo'")
.uniqueResult();
Attention, une exception est levée si plus d’une ligne est trouvée. Cette méthode
ne devrait être appliquée que pour des recherches portant sur des attributs
déclarés unique
dans le schéma de la base de données (dont la clé primaire).
Note
À partir de maintenant je donne les requêtes HQL indépendamment du code java, dans la forme « pure » que vous pouvez tester sous Hibernate Tools.
S2: base de HQL#
Supports complémentaires :
Rappelons que HQL est un langage de requêtes objet qui sert à interroger le graphe (virtuel au départ) des objets java constituant la vue orientée-objet de la base. Autrement dit, on interroge un ensemble d’objets java liés par des associations, et pas directement la base relationnelle qui permet de les matérialiser. Hibernate se charge d’effectuer les requêtes SQL pour matérialiser la partie du graphe qui satisfait la requête. Cela donne une syntaxe et une interprétation parfois différente de SQL, même si les deux langages sont en apparence très proches.
Sélection d’objets#
Toute cette section se concentre sur l’interrogation d’une seule classe.
La clause from
#
La forme la plus basique de HQL comprend la clause from
,
et indique la classe dont l’extension est sujet de la recherche:
from Film
Le point intéressant est que, en cas de hiérarchie d’héritage, il est possible d’interroger les super- et sous-classes. La liste des vidéos est obtenue par:
from Video
et la liste des films (sous-classe de Video
) par:
from FilmV
On peut rechercher tous les objets persistants avec:
from java.lang.Object
On peut créer des alias pour désigner les objets et leur appliquer des traitements de
sélection ou projection. La requête suivante définit un alias film
:
select film.titre
from Film as film
where film.annee < 2000
Le mot-clé as
est optionnel (comme la clause select
). Je l’utilise systématiquement pour plus de clarté.
Préfixer un attribut par l’alias n’est indispensable que s’il y a risque d’ambiguité, ce qui n’est pas le
cas dans l’exemple ci-dessus.
La clause where
#
Les opérateurs de comparaison sont très proches de ceux de SQL: =, <, >,
between
, in
, not between
, etc. Les opérateurs like
et not like
s’utilisent comme en SQL et
expriment la même chose.
Les opérateurs logiques sont les mêmes qu’en SQL: and
,
or
et not
, avec ordre de priorité défini par le parenthésage.
De même, HQL dispose d’une clause order by
semblable à celle de SQL.
Voici un exemple un peu complet:
from Film as film
where (titre like '%er%' and annee between 1970 and 2000)
or film.genre='Drame'
order by film.titre
Attention à la valeur NULL
ou null
. Comme en SQL, elle indique l’absence de valeur. On la teste
avec l’expression is null
, comme en SQL:
from Film as film
where film.resume is null
Note
il existe beaucoup de fonctions pré-définies que peuvent
être appliquées aux propriétés des objets: size()
, minElement()
, trunc()
,
trim()
, etc. Je vous renvoie à la documentation pour une liste complète.
Jointures implicites#
Contrairement à SQL où les critères de sélection ne peuvent concerner que les propriétés de la table interrogée, HQL permet la navigation vers des objets associés (et donc vers d’autres tables), avec une syntaxe abrégée et intuitive. On peut par exemple chercher les films dirigés par Clint Eastwood:
from Film as film
where film.realisateur.nom = 'Eastwood'
Cela ne fonctionne que pour les associations many-to-one
et one-to-one
. Vous ne pouvez pas écrire:
from Film as film
where film.roles.acteur.nom = 'Eastwood'
Attention, il n’y a pas de magie: cette syntaxe nécessite une jointure au niveau de la requête SQL générée par Hibernate, et correspond en fait à une manière dite implicite d’exprimer une jointure. Voici la requête SQL engendrée par Hibernate.
select *
from Film film0_ cross join Artiste artiste1_
where film0_.id_realisateur=artiste1_.id
and artiste1_.nom='Eastwood'
On peut bien entendu exprimer explicitement des jointures, avec des options assez riches détaillées dans la section suivante.
Exercice: expression de restrictions
Trouver lez films dont le titre comprend le mot “parrain”
Trouvez tous les drames
Trouvez tous les artistes nés avant 1970
Trouvez tous les réalisateurs (c-à-d. les artistes qui ont mis en scène un film)
S3: Les jointures#
Supports complémentaires :
Jusqu’à présent, nous prenons les objets d’une classe, ce qui correspond donc à une recherche de ligne dans une seule table pour les instancier. Comme en SQL, il est nécessaire d’exprimer des jointures si l’on veut exprimer des critères de recherche sur les objets associés.
Jointures HQL et jointures SQL#
Comme en SQL, une jointure s’exprime avec le join
.
en HQL, en exprime des jointures par navigation en exploitant les associations d’objets définies dans le mapping,
en SQL, on applique (conceptuellement) le produit cartésien de deux tables, et on restreint le résultat à des combinaisons de lignes satisfaisant certains critères.
Il faut bien réaliser qu’une jointure par HQL qui, en apparence, accède directement aux objets associés, est en fait implantée par Hibernate comme une jointure SQL. Soyons concrets avec un premier exemple: on veut tous les films mis en scène par Clint Eastwood:
select film
from Film as film
join film.realisateur as a
where a.nom='Eastwood'
On accède donc directement à l’objet associé realisateur
, sans mention
explicite de la classe Artiste
. En sous-main, Hibernate engendre et exécute
la requête SQL suivante.
select film.*
from Film film0_ inner join Artiste artiste1_
on film0_.id_realisateur=artiste1_.id
where artiste1_.nom='Eastwood'
Vous voyez que la version SQL comprend une contrainte d’égalité entre la
clé primaire de Artiste
et la clé étrangère dans Film
, contrainte
qui n’apparaît pas en HQL puisqu’elle est représentée par une association
entre objets dans le graphe virtuel. La définition du mapping permet
à Hibernate d’engendrer le code SQL correct.
On peut adopter en HQL une expression plus proche de la logique SQL, mais moins naturelle du point de vue d’une logique « graphe d’objet ».
select film
from Film as film, Artiste as a
where film.realisateur = a
and a.nom='Eastwood'
Le code SQL engendré est le même. Il n’y a donc aucune différence en termes de performance ou d’expressivité. Cette dernière version illustre une autre spécificité « objet » de HQL: on compare directement l’identité de deux objets (un artiste, un réalisateur) avec la clause:
where film.realisateur = a
et pas, comme en SQL, l’égalité des clés primaires de ces objets. Cela étant, il est possible d’effectuer directement une jointure sur les clés en HQL:
select film
from Film as film, Artiste as a
where film.realisateur.id = a.id
and a.nom='Eastwood'
Ici, id
est un mot-clé représentant la clé primaire, quel que soit
son nom réel.
Le rôle de la clause select
#
Voici un autre exemple de jointure, impliquant une association un à plusieurs. Elle sélectionne tous les films dont un des rôles est McClane:
select distinct film
from Film as film
join film.roles as role
where role.nom= 'McClane'
La clause select
prend ici une importance particulière: dans la mesure où nous accédons
aux extensions de deux classes (Film
et Role
), il est préférable de préciser
quels sont les objets que nous plaçons dans le résultat. Sans clause select
,
on obtient une liste de paires d’objets (un film, un rôle),
soit une instance de List<Object[]>
. Pour chaque élément
de la liste, le premier composant du tableau est un Film
,
le second un Rôle
.
De plus, le film est dupliqué autant de fois qu’il
a de rôles associés, ce qui rend indispensable l’utilisation
du mot-clé distinct
. Executez par exemple la requête suivante.
select film.titre, role.nom
from Film as film
join film.roles as role
On obtient autant de fois le titre du film qu’il y a de rôles. Un tel résultat est assez laborieux à traiter, et on perd en grande partie les avantages de HQL (à savoir instancier un résultat directement exploitable).
Dernier exemple, tous les films où Clint Eastwood joue un rôle:
select distinct film
from Film as film
join film.roles as role
join role.pk.acteur as acteur
where acteur.nom= 'Eastwood'
Notez le chemin role.pk.acteur
, exprimant un chemin dans le graphe des objets et composants. Je vous invite
à consulter la requête SQL engendrée par Hibernate pour cette jointure.
La clause join
de HQL#
Les jointures de HQL sont les mêmes que celles du SQL ANSI.
[inner] join
: exprime une jointure standard pour impliquer (pas forcément matérialiser) les objets associés;
left [outer] join
: exprime une jointure externe à gauche: si par exemple un film n’a pas de metteur en scène, il (le film) fera quand même partie du résultat, avec un objetrealisateur
associé ànull
;
right [outer] join
est la complémentaire deleft join
(et a donc peu d’intérêt en pratique).
Pour bien voir la différence, exécutez les deux requêtes suivantes:
from Film as film
inner join film.roles as role
et:
from Film as film
left outer join film.roles as role
La seconde devrait vous ramener aussi les films pour lesquels aucun rôle n’est connu.
Comme indiqué plus haut, cette requête renvoie une instance de List<Object[]>
,
qu’il faut ensuite décoder, pour obtenir, respectivement, des films et des rôles.
Ce qui soulève la question suivante: si, à partir de l’un des films sélectionnés, je veux accéder
aux rôles par la collection film.roles
, Hibernate a-t-il déjà instancié cette collection,
ou va-t-il devoir à nouveau accéder à ces rôles avec une requête SQL?.
La réponse est: une nouvelle requête sera effectuée pour trouver les rôles du film, ce qui semble dommage car la jointure SQL sera effectuée deux fois. Les requêtes HQL que nous présentons de créent pas de graphe, mais instancient simplement les objets sélectionnés, sans lien entre eux. Dit autrement: le fait d’effectuer une jointure entre un film et des rôles n’implique pas que le graphe film-rôles est matérialisé dans le cache de premier niveau.
Ce n’est pas si illogique que cela en a l’air, si on prend en compte le fait qu’il est possible d’appliquer des restrictions sur les objets sélectionnés, comme dans l’exemple ci-dessous.
from Film as film
left outer join film.roles as role
where role.nom='McClane'
Si on matérialisait le graphe à partir des objets sélectionnés, la collection
roles
de chaque film serait incomplète puisqu’elle ne comprendrait
que ceux dont le nom est McClane
. En résumé, il faut bien distinguer
deux motivations différentes, et partiellement incompatibles:
la sélection d’objets satisfaisant des critères de restriction;
la matérialisation d’une partie du sous-graphe de données.
Les requêtes HQL que nous présentons ici correspondent à la première
motivation. Il est possible d’utiliser HQL aussi pour la matérialisation
du graphe de données, avec une option fetch
que nous étudierons dans le
prochain chapitre.
Important
Retenez que ces deux motivations peuvent être incompatibles: si je restreins avec la clause where les objets sélectionnés (les films avec un rôle McClane), je ne peux pas obtenir en même temps un graphe complet (tous les rôles des films, dont un des rôles est McClane).
En résumé, concentrez-vous pour l’instant sur l’utilisation de HQL pour
sélectionner et matérialiser les objets qui vous intéressent, sans
chercher à optimiser les performances ou éliminer les redondances
de requêtes SQL engendrées. Cela revient à toujours
utiliser la clause select
pour garder les objets qui vous intéressent,
et à utiliser les jointures pour exprimer des restrictions. Par exemple:
select distinct film
from Film as film
left outer join film.roles as role
where role.nom='McClane'
Comme en SQL, une autre manière d’exprimer une jointure est d’utiliser des sous-requêtes, qui sont présentées un peu plus loin.
Exercice: pour quelques jointures de plus
Trouvez les films avec l’acteur Bruce Willis.
Affichez les noms des acteurs ayant joué le rôle de “Jack Dawson”, et le metteur en scène qui les a dirigés.
Affichez la liste des metteurs en scène, et les films qu’ils ont réalisés
Affichez les films dans lesquels le metteur en scène est également un acteur (pensez: « HQL, langage objet »!)
Affichez la liste des genres, et pour chaque genre les films correspondant.
S4: Compléments#
Supports complémentaires :
Nous avons vu l’essentiel de HQL dans ce qui précède. Voici quelques compléments utiles.
Sous-requêtes#
Comme en SQL, HQL permet l’expression de sous-requêtes, ce qui revient souvent à une nouvelle manière d’exprimer des jointures. Voici donc une nouvelle manière d’obtenir les films dirigés par Clint Eastwood.
from Film as film
where film.realisateur in (from Artiste where nom='Eastwood')
Par rapport à l’expression standard avec un join
, il existe une différence
qui mérite d’être mentionnée: comme la classe Artiste
n’apparaît que
dans la sous-requête, les instances de Artiste
ne figurent pas dans le résultat.
Il n’est donc pas nécessaire d’utiliser une clause select
pour se restreindre
aux films.
Voici une autre version de la requête qui ramène les films dans lesquels figure un rôle “McClane”.
from Film as film
where film in (select role.pk.film from Role as role
where role.nom='McClane')
Et une autre version de la requête qui recherche les films
dirigés par Clint Eastwood, cette fois avec une sous-requête
exists
.
select film.titre
from Film as film
where exists (from Artiste as a where a = film.realisateur and a.nom='Eastwood')
Il est bien clair que cette expression est très compliquée par rapport à celle basée sur la jointure par navigation (et en particulier la jointure implicite, voir ci-dessus).
Les sous-requêtes en HQL sont tout à fait similaires à celles de SQL. Je n’entre donc pas dans l’énumération des diverses possibilités. Pour rappel, les quantificateurs suivants sont composables avec les opérateurs de comparaison, et s’appliquent à des sous-requêtes qui ramènent plusieurs lignes.
any
: vrai si au moins une ligne ramenée par la sous-requête satisfait la comparaison;
all
: vrai si toutes les lignes ramenées par la sous-requête satisfontt la comparaison.
Des synonymes pour any
sont in
et some
. Voici deux exemples. Le premier
recherche les films dont au moins un acteur est né avant 1940.
from Film as film
where 1940 > any (select role.pk.acteur.anneeNaissance
from Role as role
where role.pk.film=film)
Il est beaucoup plus simple (et plus lisible) d’exprimer ce genre de requête
avec un join
. L’utilisation de all
(pour: « les films dont tous
les acteurs sont nés avant 1940 ») paraît plus justifiée:
from Film as film
where 1940 > all (select role.pk.acteur.anneeNaissance
from Role as role
where role.pk.film=film)
Comme en SQL, il existe plusieurs syntaxes possibles pour une même requête. Par
exemple, celle ci-dessus s’exprime aussi avec not exists
(à vous de jouer).
Exercice: requêtes imbriquées
Les requêtes suivantes doivent être exprimées avec des sous-requêtes
Quels internautes n’ont donné que des notes supérieures à 3?
Quels sont les films dans lesquels joue Brad Pitt?
Et quels sont les films qui n’ont pas obtenu une note de 5?
Création d’objet spécifiques#
Revenons sur les requêtes ad hoc qui constituent des résultats constitués de projection sur certains attributs. Voici un exemple
select film.titre, film.annee, film.realisateur.prenom, film.realisateur.nom
from Film as film
Quel est l’intérêt de cette requête? A priori elle implique un travail de décodage fastidieux du résultat, constitué d’une liste de tableaux, chaque tableau contenant 4 objets correspondant aux 4 attributs projetés.
Il existe quand même un avantage potentiel: comme la requête ne nécessite pas la création d’objets persistants, on évite le placement de ces objets dans le cache de la session, avec le (modeste) surcoût induit par le test d’existence, et l’insertion dans la table de hachage.
On gagne donc (un peu) en performance. Il se peut que dans certains cas le gain vaille le surcroît de programmation nécessaire.
Regroupement et agrégation#
Le group by
, le having
et les fonctions d’agrégation sont identiques
en HQL et en SQL (ou, pour être plus précis, elles ne présentent aucune spécificité
supplémentaire par rapport à celles que nous avons déjà vues). Quelques exemples
suffiront.
La note moyenne des films
select avg(note) from NotationLes films et leur nombre d’acteurs
select film, count(*) from Film as film join film.roles as role group by filmNotez qu’on aimerait faire
select film, count(film.roles) from Film as film
, mais HQL n’accepte pas cette syntaxe.Les metteurs en scène ayant réalisé au moins trois films
select artiste.nom, count(*) from Artiste as artiste join artiste.filmsRealises as film group by artiste having count(*) > 3
Exercice: requêtes imbriquées
Les internautes et le nombre de films qu’ils ont notés
Donnez les films avec au moins 4 rôles.
Films dont la note moyenne est supérieure à la note moyenne de tous les films.