Hibernate fourni un langage d'interrogation extrêmement puissant qui ressemble (et c'est voulu) au SQL. Mais ne soyez pas distraits par la syntaxe ; HQL est totalement orienté objet, comprenant des notions d'héritage, de polymorphisme et d'association.
Les requêtes sont insensibles à la casse, à l'exception des noms des classes Java et des propriétés. Ainsi, SeLeCT est identique à sELEct et à SELECT mais net.sf.hibernate.eg.FOO n'est pas identique net.sf.hibernate.eg.Foo et foo.barSet n'est pas identique à foo.BARSET.
Ce guide utilise les mots clés HQL en minuscule. Certains utilisateurs trouvent les requêtes écrites avec les mots clés en majuscule plus lisibles, mais nous trouvons cette convention pénible lorsqu'elle est lue dans du code Java.
La requête Hibernate la plus simple est de la forme :
from eg.Cat
qui retourne simplement toutes les instances de la classe eg.Cat.
La plupart du temps, vous devrez assigner un alias puisque vous voudrez faire référence à Cat dans d'autres parties de la requête.
from eg.Cat as cat
Cette requête assigne l'alias cat à l'instance Cat, nous pouvons donc utiliser cet alias ailleurs dans la requête. Le mot clé as est optionnel ; nous aurions pu écrire
from eg.Cat cat
Plusieurs classes peuvent apparaître, ce qui conduira à un produit cartésien (encore appelé jointures croisées).
from Formula, Parameter
from Formula as form, Parameter as param
C'est une bonne pratique que de nommer les alias dans les requêtes en utilisant l'initiale en miniscule, ce qui a le mérite d'être en phase avec les standards de nommage Java pour les variables locales (domesticCat).
On peut aussi assigner des alias à des entités associées, ou même aux éléments d'une collection de valeurs, en utilisant un join (jointure).
from eg.Cat as cat inner join cat.mate as mate left outer join cat.kittens as kitten from eg.Cat as cat left join cat.mate.kittens as kittens from Formula form full join form.parameter param
Les types de jointures supportées sont celles de ANSI SQL
inner join (jointure fermée)
left outer join (jointure ouverte par la gauche)
right outer join (jointure ouverte par la droite)
full join (jointure ouverte totalement - généralement inutile)
Les constructions des jointures inner join, left outer join et right outer join peuvent être abbrégées.
from eg.Cat as cat join cat.mate as mate left join cat.kittens as kitten
Par ailleurs, une jointure "fetchée" (rapportée) permet d'initialiser les associations ou collections de valeurs en même temps que leur objet parent, le tout n'utilisant qu'un seul Select. Ceci est particulièrement utile dans le cas des collections. Ce système permet de surcharger les déclarations "lazy" et "outer-join" des fichiers de mapping pour les associations et collections.
from eg.Cat as cat inner join fetch cat.mate left join fetch cat.kittens
Une jointure "fetchée" (rapportée) n'a généralement pas besoin de se voir assigner un alias puisque les objets associés n'ont pas à être utilisés dans les autres clauses. Notez aussi que les objets associés ne sont pas retournés directement dans le résultat de la requête mais l'on peut y accéder via l'objet parent.
Notez que, dans l'implémentation courante, seule une seule collection peut être "fetchée" par requête (une autre stratégie ne serait pas performante). Notez aussi que le mot-clé fetch ne peut pas être utilisé lorsque l'on appelle scroll() ou iterate(). Notez enfin que full join fetch et right join fetch ne sont pas utiles en général.
La clause select sélectionne les objets et propriétés qui doivent être retournés dans le résultat de la requête. Soit :
select mate from eg.Cat as cat inner join cat.mate as mate
La requête recherchera les mates liés aux Cats. Vous pouvez explimer la requête d'une manière plus compacte :
select cat.mate from eg.Cat cat
Vous pouvez même sélectionner les éléments d'une collection en utilisant la fonction elements. La requête suivante retourne tous les kittens de chaque cat.
select elements(cat.kittens) from eg.Cat cat
Les requêtes peuvent retourner des propriétés de n'importe quel type, même celles de type composant (component).
select cat.name from eg.DomesticCat cat where cat.name like 'fri%' select cust.name.firstName from Customer as cust
Les requêtes peuvent retourner plusieurs objets et/ou propriétés sous la forme d'un tableau du type Object[]
select mother, offspr, mate.name from eg.DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr
ou sous la forme d'un objet Java typé
select new Family(mother, mate, offspr) from eg.DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr
à condition que la classe Family possède le constructeur approprié.
Les requêtes HQL peuvent aussi retourner le résultat de fonctions d'aggrégation sur les propriétés :
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat) from eg.Cat cat
Les collections peuvent aussi apparaître à l'intérieur des fonctions d'aggrégation dans la clause select
select cat, count( elements(cat.kittens) ) from eg.Cat cat group by cat
Les fonctions supportées sont
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
Les mots clé distinct et all peuvent être utilisés et ont la même signification qu'en SQL.
select distinct cat.name from eg.Cat cat select count(distinct cat.name), count(cat) from eg.Cat cat
Un requête comme :
from eg.Cat as cat
retourne non seuleument les instances de Cat, mais aussi celles des sous classes comme DomesticCat. Les requêtes Hibernate peuvent nommer n'importe quelle classe ou interface Java dans la clause from. La requête retournera les instances de toutes les classes persistantes qui étendent cette classe ou implémente cette interface. La requête suivante retournera tous les objets persistants :
from java.lang.Object o
L'interface Named peut être implémentée par plusieurs classes persistantes:
from eg.Named n, eg.Named m where n.name = m.name
Notez que ces deux dernières requêtes nécessitent plus d'un SELECT SQL. Ce qui signifie que la clause order by ne trie pas correctement la totalité des résultats (cela signifie aussi que vous ne pouvez exécuter ces requêtes en appelant Query.scroll()).
La clause where vous permet de réduire la liste des instances retournées.
from eg.Cat as cat where cat.name='Fritz'
retourne les instances de Cat dont name est égale à 'Fritz'.
select foo from eg.Foo foo, eg.Bar bar where foo.startDate = bar.date
retournera les instances de Foo pour lesquelles il existe une instance de bar avec la propriété date est égale à la propriété startDate de Foo. Les expressions utilisant la navigation rendent la clause where extrêmement puissante. Soit :
from eg.Cat cat where cat.mate.name is not null
Cette requête se traduit en SQL par une jointure interne à une table. Si vous souhaitez écrire quelque chose comme :
from eg.Foo foo where foo.bar.baz.customer.address.city is not null
vous finiriez avec une requête qui nécessiterait quatre jointures en SQL.
L'opérateur = peut être utilisé pour comparer aussi bien des propriétés que des instances :
from eg.Cat cat, eg.Cat rival where cat.mate = rival.mate select cat, mate from eg.Cat cat, eg.Cat mate where cat.mate = mate
La propriété spéciale (en minuscule) id peut être utilisée pour faire référence à l'identifiant d'un objet (vous pouvez aussi utiliser le nom de cette propriété).
from eg.Cat as cat where cat.id = 123 from eg.Cat as cat where cat.mate.id = 69
La seconde requête est particulièrement efficace. Aucune jointure n'est nécessaire !
Les propriétés d'un identifiant composé peuvent aussi être utilisées. Supposez que Person ait un identifiant composé de country et medicareNumber.
from bank.Person person where person.id.country = 'AU' and person.id.medicareNumber = 123456 from bank.Account account where account.owner.id.country = 'AU' and account.owner.id.medicareNumber = 123456
Une fois de plus, la seconde requête ne nécessite pas de jointure.
De même, la propriété spéciale class interroge la valeur discriminante d'une instance dans le cas d'une persistance polymorphique. Le nom d'une classe Java incorporée dans la clause where sera traduite par sa valeur discriminante.
from eg.Cat cat where cat.class = eg.DomesticCat
Vous pouvez aussi spécifier les propriétés des composants ou types utilisateurs composés (components, composite user types etc). N'essayez jamais d'utiliser un expression de navigation qui se terminerait par une propriété de type composant (qui est différent d'une propriété d'un composant). Par exemple, si store.owner est une entité avec un composant address
store.owner.address.city // correct store.owner.address // erreur!
Un type "any" possède les propriétés spéciales id et class, qui nous permettent d'exprimer une jointure de la manière suivante (où AuditLog.item est une propriété mappée avec <any>).
from eg.AuditLog log, eg.Payment payment where log.item.class = 'eg.Payment' and log.item.id = payment.id
Dans la requête précédente, notez que log.item.class et payment.class feraient référence à des valeurs de colonnes de la base de données complètement différentes.
Les expressions permises dans la clause where incluent la plupart des choses que vous pouvez utiliser en SQL :
opérateurs mathématiques +, -, *, /
opérateur de comparaison binaire =, >=, <=, <>, !=, like
opérateurs logiques and, or, not
concatenation de chaîne de caractères ||
fonctions SQL scalaires comme upper() et lower()
Parenthèses ( ) indiquant un regroupement
in, between, is null
paramètres JDBC IN ?
paramètres nommées :name, :start_date, :x1
littéral SQL 'foo', 69, '1970-01-01 10:00:01.0'
Constantes Java public static final eg.Color.TABBY
in et between peuvent être utilisés comme suit :
from eg.DomesticCat cat where cat.name between 'A' and 'B' from eg.DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
et la forme négative peut être écrite
from eg.DomesticCat cat where cat.name not between 'A' and 'B' from eg.DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
De même, is null et is not null peuvent être utilisés pour tester les valeurs nulle.
Les Booléens peuvent être facilement utilisés en déclarant les substitutions de requêtes dans la configuration Hibernate :
<property name="hibernate.query.substitutions">true 1, false 0</property>
Ce qui remplacera les mots clés true et false par 1 et 0 dans la traduction SQL du HQL suivant:
from eg.Cat cat where cat.alive = true
Vous pouvez tester la taille d'une collection par la propriété spéciale size, ou la fonction spéciale size().
from eg.Cat cat where cat.kittens.size > 0 from eg.Cat cat where size(cat.kittens) > 0
Pour les collections indexées, vous pouvez faire référence aux indices minimum et maximum en utilisant minIndex and maxIndex. De manière similaire, vous pouvez faire référence aux éléments minimum et maximum d'une collection de type basiques en utilisant minElement et maxElement.
from Calendar cal where cal.holidays.maxElement > current date
Ceci existe aussi sous forme de fonctions (qui, contrairement à l'écriture précédente, n'est pas sensible à la casse):
from Order order where maxindex(order.items) > 100 from Order order where minelement(order.items) > 10000
Les fonctions SQL any, some, all, exists, in supportent que leur soient passées l'élément, l'index d'une collection (fonctions elements et indices) ou le résultat d'une sous requête (voir ci dessous).
select mother from eg.Cat as mother, eg.Cat as kit where kit in elements(foo.kittens) select p from eg.NameList list, eg.Person p where p.name = some elements(list.names) from eg.Cat cat where exists elements(cat.kittens) from eg.Player p where 3 > all elements(p.scores) from eg.Show show where 'fizard' in indices(show.acts)
Notez que l'écriture de - size, elements, indices, minIndex, maxIndex, minElement, maxElement - ont un usage restreint :
dans la clause where : uniquement pour les bases de données qui supportent les requêtes imbriquées (sous requêtes)
dans la clause select : uniquement elements et indices ont un sens
Les éléments de collections indexées (arrays, lists, maps) peuvent être référencés via index (dans une clause where seuleuement) :
from Order order where order.items[0].id = 1234 select person from Person person, Calendar calendar where calendar.holidays['national day'] = person.birthDay and person.nationality.calendar = calendar select item from Item item, Order order where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11 select item from Item item, Order order where order.items[ maxindex(order.items) ] = item and order.id = 11
L'expression entre [] peut même être une expression arithmétique.
select item from Item item, Order order where order.items[ size(order.items) - 1 ] = item
HQL propose aussi une fonction index() interne, pour les éléments d'une association one-to-many ou d'une collections de valeurs.
select item, index(item) from Order order join order.items item where index(item) < 5
Les fonctions SQL scalaires supportées par la base de données utilisée peuvent être utilisées
from eg.DomesticCat cat where upper(cat.name) like 'FRI%'
Si vous n'êtes pas encore convaincu par tout cela, imaginez la taille et l'illisibilité qui caractériseraient la transformation SQL de la requête HQL suivante :
select cust from Product prod, Store store inner join store.customers cust where prod.name = 'widget' and store.location.name in ( 'Melbourne', 'Sydney' ) and prod = all elements(cust.currentOrder.lineItems)
Un indice : cela donnerait quelque chose comme
SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order FROM customers cust, stores store, locations loc, store_customers sc, product prod WHERE prod.name = 'widget' AND store.loc_id = loc.id AND loc.name IN ( 'Melbourne', 'Sydney' ) AND sc.store_id = store.id AND sc.cust_id = cust.id AND prod.id = ALL( SELECT item.prod_id FROM line_items item, orders o WHERE item.order_id = o.id AND cust.current_order = o.id )
La liste retounée par la requête peut être triée par n'importe quelle propriété de la classe ou du composant retourné :
from eg.DomesticCat cat order by cat.name asc, cat.weight desc, cat.birthdate
Le mot optionnel asc ou desc indique respectivement si le tri doit être croissant ou décroissant.
Si la requête retourne des valeurs aggrégées, celles ci peuvent être groupées par propriété ou composant :
select cat.color, sum(cat.weight), count(cat) from eg.Cat cat group by cat.color select foo.id, avg( elements(foo.names) ), max( indices(foo.names) ) from eg.Foo foo group by foo.id
Note: vous pouvez aussi utiliser l'écriture elements et indices dans une clause select, même pour des bases de données qui ne supportent pas les sous requêtes.
Une clause having est aussi permise.
select cat.color, sum(cat.weight), count(cat) from eg.Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
Les fonctions SQL et les fonctions d'aggrégations sont permises dans les clauses having et order by, si elles sont supportées par la base de données (ce que ne fait pas MySQL par exemple).
select cat from eg.Cat cat join cat.kittens kitten group by cat having avg(kitten.weight) > 100 order by count(kitten) asc, sum(kitten.weight) desc
Notez que ni la clause group by ni la clause order by ne peuvent contenir d'expressions arithmétiques.
Pour les bases de données le supportant, Hibernate supporte les sous requêtes dans les requêtes. Une sous requête doit être entre parenthèses (souvent pour un appel à une fonction d'agrégation SQL) Même les sous requêtes corrélées (celles qui font référence à un alias de la requête principale) sont supportées.
from eg.Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from eg.DomesticCat cat ) from eg.DomesticCat as cat where cat.name = some ( select name.nickName from eg.Name as name ) from eg.Cat as cat where not exists ( from eg.Cat as mate where mate.mate = cat ) from eg.DomesticCat as cat where cat.name not in ( select name.nickName from eg.Name as name )
Les requêtes Hibernate peuvent être relativement puissantes et complexes. En fait, la puissance du langage de requêtage est l'un des avantages principaux d'Hibernate. Voici quelques exemples très similaires aux requêtes que nous avons utilisées lors d'un récent projet. Notez que la plupart des requêtes que vous écrirez seront plus simples que les exemples suivantes !
La requête suivante retourne l'id de commande (order), le nombre d'articles (items) et la valeur totale de la commande (order) pour toutes les commandes non payées d'un client (customer) particulier pour un total minimum donné, le tout trié par la valeur totale. La requête SQL générée sur les tables ORDER, ORDER_LINE, PRODUCT, CATALOG et PRICE est composée de quatre jointures interne ainsi que d'une sous requête non corrélée.
select order.id, sum(price.amount), count(item) from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as price where order.paid = false and order.customer = :customer and price.product = product and catalog.effectiveDate < sysdate and catalog.effectiveDate >= all ( select cat.effectiveDate from Catalog as cat where cat.effectiveDate < sysdate ) group by order having sum(price.amount) > :minAmount order by sum(price.amount) desc
Quel monstre ! En principe, nous ne sommes pas très fan des sous requêtes, la requête ressemblait donc plutôt à cela :
select order.id, sum(price.amount), count(item) from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as price where order.paid = false and order.customer = :customer and price.product = product and catalog = :currentCatalog group by order having sum(price.amount) > :minAmount order by sum(price.amount) desc
La requête suivante compte le nombre de paiements (payments) pour chaque status, en excluant les paiements dans le status AWAITING_APPROVAL où le changement de status le plus récent à été fait par l'utilisateur courant. En SQL, cette requête effectue deux jointures internes et des sous requêtes corrélées sur les tables PAYMENT, PAYMENT_STATUS et PAYMENT_STATUS_CHANGE.
select count(payment), status.name from Payment as payment join payment.currentStatus as status join payment.statusChanges as statusChange where payment.status.name <> PaymentStatus.AWAITING_APPROVAL or ( statusChange.timeStamp = ( select max(change.timeStamp) from PaymentStatusChange change where change.payment = payment ) and statusChange.user <> :currentUser ) group by status.name, status.sortOrder order by status.sortOrder
Si nous avions mappé la collection statusChanges comme une list, au lieu d'un set, la requête aurait été plus facile à écrire.
select count(payment), status.name from Payment as payment join payment.currentStatus as status where payment.status.name <> PaymentStatus.AWAITING_APPROVAL or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser group by status.name, status.sortOrder order by status.sortOrder
La requête qui suit utilise la fonction de MS SQL isNull() pour retourner tous les comptes (accounts) et paiements (payments) impayés pour l'organisation à laquelle l'uilisateur (user) courant appartient. Elle est traduite en SQL par trois jointures internes, une jointure externe ainsi qu'une sous requête sur les tables ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION et ORG_USER.
select account, payment from Account as account left outer join account.payments as payment where :currentUser in elements(account.holder.users) and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID) order by account.type.sortOrder, account.accountNumber, payment.dueDate
Pour d'autres base de données, nous aurions dû faire sans la sous requête (corrélée)
select account, payment from Account as account join account.holder.users as user left outer join account.payments as payment where :currentUser = user and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID) order by account.type.sortOrder, account.accountNumber, payment.dueDate
Vous pouvez compter le nombre de résultats d'une requête sans les retourner :
( (Integer) session.iterate("select count(*) from ....").next() ).intValue()
Pour trier les résultats par la taille d'une collection, utilisez :
select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name order by count(msg)
Si votre base de données supporte les sous requêtes, vous pouvez placer des conditions sur la taille de la sélection dans la clause where de votre requête :
from User usr where size(usr.messages) >= 1
Si votre base de données ne supporte pas les sous requêtes, utilisez :
select usr.id, usr.name from User usr.name join usr.messages msg group by usr.id, usr.name having count(msg) >= 1
Cette solution ne peut pas retourner un User avec zéro message à cause de la jointure interne, la forme suivante peut donc être utile :
select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name having count(msg) = 0
Les propriétés d'un JavaBean peuvent être injectées dans les paramètres nommés d'un requête :
Query q = s.createQuery("from foo in class Foo where foo.name=:name and foo.size=:size"); q.setProperties(fooBean); // fooBean possède getName() and getSize() List foos = q.list();
Les collections sont paginables via l'utilisation de l'interface Query avec un filtre :
Query q = s.createFilter( collection, "" ); // the trivial filter q.setMaxResults(PAGE_SIZE); q.setFirstResult(PAGE_SIZE * pageNumber); List page = q.list();
Les éléments d'une collection peuvent être triés ou groupés en utilisant un filtre de requête :
Collection orderedCollection = s.filter( collection, "order by this.amount" ); Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );
Vous pouvez récupérer la taille d'une collection sans l'initialiser :
( (Integer) session.iterate("select count(*) from ....").next() ).intValue();