Hibernate è dotato di un linguaggio di interrogazione estremamente potente che (del tutto intenzionalmente) assomiglia molto all'SQL. Ma la sintassi non deve ingannare: l'HQL è pienamente orientato agli oggetti, e comprende nozioni come l'ereditarietà, il polimorfismo e l'associazione.
Le interrogazioni non distinguono tra maiuscole e minuscole, eccetto per i nomi delle classi java e delle proprietà. Quindi SeLeCT è la stessa cosa di sELEct che è la stessa cosa di SELECT ma net.sf.hibernate.eg.FOO non è net.sf.hibernate.eg.Foo e foo.barSet non è foo.BARSET.
Questo manuale fa uso di parole chiave HQL in lettere minuscole. Alcuni utenti trovano che le interrogazioni con parole chiave in maiuscolo siano più leggibili, ma troviamo che questa convenzione sia brutta, quando utilizzata in interrogazioni annidate in codice java.
L'interrogazione più semplice possibile in Hibernate ha la forma:
from eg.Cat
che restituisce semplicemente tutte le istanze della classe eg.Cat.
La maggior parte delle volte, avrete bisogno di assegnare un sinonimo, poiché vorrete fare riferimento al Cat in altre parti dell'interrogazione.
from eg.Cat as cat
Questa query assegna il sinonimo cat alle istanze di Cat, in modo da poter usare quel sinonimo più avanti nell'interrogazione. La parola chiave as è opzionale, potremmo anche scrivere:
from eg.Cat cat
Possono apparire anche classi multiple, il che risulta in un prodotto cartesiano o join "incrociato".
from Formula, Parameter
from Formula as form, Parameter as param
Viene considerata una buona abitudine dare ai sinonimi delle interrogazioni nomi che comincino con lettere minuscole, in maniera coerente con gli standard di denominazione di Java per le variabili locali (ad esempio domesticCat).
Possiamo anche assegnare sinonimi ad entità associate, o anche ad elementi di una collezione di valori, usando un join.
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
I tipi di join supportati sono presi in prestito dall'SQL ANSI
inner join
left outer join
right outer join
full join (di solito inutile)
I costrutti inner join, left outer join e right outer join possono venire abbreviati.
from eg.Cat as cat join cat.mate as mate left join cat.kittens as kitten
In aggiunta, un join di tipo "fetch" (raccolta) consente di inizializzare le associazioni o le collezioni insieme agli oggetti genitori, usando una singola select. Questo è particolarmente utile nel caso di una collezione. Sovrascrive in maniera efficace le dichiarazioni dei join esterni (outer join) e della raccolta differita (lazy) del file di mappaggio per le associazioni e le collezioni.
from eg.Cat as cat inner join fetch cat.mate left join fetch cat.kittens
Un "fetch join" (join con raccolta) non ha solitamente bisogno di assegnare un sinonimo, perché gli oggetti associati non dovrebbero venire usati nella clausola where (né in un'altra clausola qualsiasi). Nello stesso modo, gli oggetti associati non vengono restituiti direttamente nei risultati della query. Possono, invece, essere raggiunti tramite l'oggetto genitore
Notate che, nell'implementazione corrente, solo un ruolo di collezione può essere concretizzato ("fetched") in una interrogazione (qualsiasi altra cosa non sarebbe performante). Notate anche che il costrutto fetch non può essere usato in interrogazioni chiamate usando scroll() o iterate(). Notate infine che full join fetch e right join fetch non hanno senso.
La clausola select sceglie quali oggetti e proprietà vanno restituiti nel set dei risultati della query. Considerate che:
select mate from eg.Cat as cat inner join cat.mate as mate
La query selezionerà gli amici (mates) dei gatti (Cats). In realtà è possibile esprimere la stessa interrogazione in maniera più compatta come:
select cat.mate from eg.Cat cat
Potete anche selezionare elementi di una collezione, usando la funzione speciale elements. L'interrogazione seguente restituisce tutti i gattini (kittens) di ogni gatto (cat).
select elements(cat.kittens) from eg.Cat cat
Le interrogazioni possono restituire proprietà di qualsiasi tipo di valore, comprese le proprietà di tipo componente:
select cat.name from eg.DomesticCat cat where cat.name like 'fri%' select cust.name.firstName from Customer as cust
Le interrogazioni possono restituire oggetti multipli e/o proprietà come un array di tipo Object[]
select mother, offspr, mate.name from eg.DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr
o come un oggetto java tipizzato
select new Family(mother, mate, offspr) from eg.DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr
purché ovviamente Family abbia un costruttore appropriato.
Le query HQL possono anche restituire i risultati di funzioni aggregate sulle proprietà:
select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat) from eg.Cat cat
Le collezioni possono anche apparire all'interno di funzioni aggregate nella clausola select.
select cat, count( elements(cat.kittens) ) from eg.Cat cat group by cat
Le funzioni aggregate supportate sono
avg(...), sum(...), min(...), max(...)
count(*)
count(...), count(distinct ...), count(all...)
Le parole chiave distinct e all possono essere usate con la stessa semantica dell'SQL.
select distinct cat.name from eg.Cat cat select count(distinct cat.name), count(cat) from eg.Cat cat
Una interrogazione come:
from eg.Cat as cat
non restituisce solo istanze di Cat, ma anche delle sottoclassi come DomesticCat. Le interrogazioni di Hibernate possono indicare qualsiasi classe o interfaccia Java nella clausola from. L'interrogazione restituirà istanze di tutte le classi persistenti che estendono quella classe o implementano l'interfaccia. La prossima interrogazione restituisce tutti gli oggetti persistenti:
from java.lang.Object o
L'interfaccia Named potrebbe essere implementata da diverse classi persistenti:
from eg.Named n, eg.Named m where n.name = m.name
Notate che queste ultime due interrogazioni richiederanno più di una SELECT SQL. Questo significa che la clausola order by non ordinerà correttamente l'intero insieme dei risultati. (e significa anche che non potete chiamare le query usando Query.scroll().)
La clausola where consente di limitare la lista di istanze rese da una interrogazione.
from eg.Cat as cat where cat.name='Fritz'
restituisce le istanze di Cat il cui nome (name) è 'Fritz'.
select foo from eg.Foo foo, eg.Bar bar where foo.startDate = bar.date
restituirà tutte le istanze di Foo per le quali esiste una istanza di bar con una proprietà date uguale alla proprietà startDate del Foo. Le espressioni a percorso composto fanno sì che la clausola where sia estremamente potente. Considerate:
from eg.Cat cat where cat.mate.name is not null
Questa interrogazione si traduce in una query SQL con un join di tabella (interno) Se doveste scrivere una cosa come
from eg.Foo foo where foo.bar.baz.customer.address.city is not null
otterreste una query che richiederebbe quattro join di tabella in SQL.
L'operatore = può essere usato per confrontare non solo proprietà, ma anche istanze:
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 proprietà speciale (in minuscolo) id può essere usata per fare riferimento all'identificatore univoco di un oggetto. (potete anche usare il suo nome di proprietà)
from eg.Cat as cat where cat.id = 123 from eg.Cat as cat where cat.mate.id = 69
La seconda query è efficiente. Non è richiesto un join di tabella!
Possono anche essere usate le proprietà di identificatori compositi. Supponete che Person abbia un identificatore composto che consiste in country e 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
Ancora una volta, la seconda interrogazione non richiede join di tabella.
Nello stesso modo, la proprietà speciale class accede al valore del discriminatore di una istanza nel caso della persistenza polimorfica. Un nome di classe java annidato nella clausola where verrà tradotto nel suo valore di discriminazione.
from eg.Cat cat where cat.class = eg.DomesticCat
Potete anche specificare proprietà o componenti o tipi utente compositi (e di componenti di componenti, ecc.). Non tentate di utilizzare una espressione di percorso che finisca in una proprietà di tipo di componente (in opposizione ad una proprietà di un componente). Ad esempio, se store.owner è una entità con un componente indirizzo (address)
store.owner.address.city // okay store.owner.address // error!
Un tipo "any" ha le proprietà speciali id e class, che consentono di esprimere un join nel modo seguente (in cui AuditLog.item è una proprietà mappata con <any>).
from eg.AuditLog log, eg.Payment payment where log.item.class = 'eg.Payment' and log.item.id = payment.id
Notate che log.item.class e payment.class possono fare riferimento ai valori di colonne di database completamente diverse nella query precedente.
Le espressioni consentite nella clausola where includono la maggior parte delle cose che si scriverebbero in SQL:
operatori matematici +, -, *, /
operatori di confronto binario =, >=, <=, <>, !=, like
operazioni logiche and, or, not
concatenamento di stringhe ||
funzioni scalari SQL come upper() e lower()
le parentesi ( ) indicano i raggruppamenti
in, between, is null
parametri di ingresso JDBC ?
parametri con nome :name, :start_date, :x1
letterali SQL 'foo', 69, '1970-01-01 10:00:01.0'
costanti Java public static final come eg.Color.TABBY
in e between possono essere usati così:
from eg.DomesticCat cat where cat.name between 'A' and 'B' from eg.DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )
e le corrispondenti forme negative possono essere scritte
from eg.DomesticCat cat where cat.name not between 'A' and 'B' from eg.DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )
Nello stesso modo, is null e is not null possono essere usati per testare i valori null.
I booleani possono essere utilizzati facilmente nelle espressioni dichiarando delle sostituzioni HQL nella configurazione di hibernate:
<property name="hibernate.query.substitutions">true 1, false 0</property>
Questo sostituirà le parole chiave true e false con i letterali 1 and 0 nell'SQL tradotto da questo HQL:
from eg.Cat cat where cat.alive = true
Potete controllare la dimensione di una collezione con la proprietà speciale size, o la funzione speciale size().
from eg.Cat cat where cat.kittens.size > 0 from eg.Cat cat where size(cat.kittens) > 0
Per le collezioni indicizzate, potete fare riferimento agli indici minimo e massimo usando minIndex e maxIndex. Nello stesso modo, potete fare riferimento agli elementi minimo e massimo di una collezione di un tipo di base usando minElement e maxElement.
from Calendar cal where cal.holidays.maxElement > current date
Ci sono anche le forme funzionali (le quali, a differenza dei costrutti qui sopra, non sono sensibili a maiuscole e minuscole):
from Order order where maxindex(order.items) > 100 from Order order where minelement(order.items) > 10000
Le funzioni SQL any, some, all, exists, in sono supportate quando viene loro passato l'insieme degli elementi o degli indici di una collezione (con le funzioni elements e indices) o il risultato di una sotto-interrogazione (vedete oltre).
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)
Notate che questi costrutti - size, elements, indices, minIndex, maxIndex, minElement, maxElement - hanno alcune restrizioni di utilizzo:
in una clausola where: solo per database con subselect
in una clausola select: solo elements e indices hanno senso
Gli elementi delle collezioni indicizzate (array, liste, mappe) possono essere reperiti tramite il loro indice (solo in una clausola where):
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
Le espressioni all'interno di [] possono anche essere espressioni matematiche.
select item from Item item, Order order where order.items[ size(order.items) - 1 ] = item
L'HQL fornisce anche la funzione predefinita index(), per gli elementi di una associazione uno-a-molti o una collezione di valori.
select item, index(item) from Order order join order.items item where index(item) < 5
Possono essere usate le funzioni scalari SQL supportate dal database sottostante
from eg.DomesticCat cat where upper(cat.name) like 'FRI%'
Se non siete ancora convinti da tutto questo, pensate a quanto più lunga e meno leggibile sarebbe la query seguente se dovesse essere espressa in SQL:
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)
Suggerimento: qualcosa come
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 lista restituita da una query può essere ordinata secondo una qualsiasi proprietà di una delle classi restituite o dei componenti:
from eg.DomesticCat cat order by cat.name asc, cat.weight desc, cat.birthdate
Gli elementi opzionali asc o desc indicano rispettivamente ordine ascendente o discendente.
Una interrogazione che renda valori aggregati può essere raggruppata in base a una proprietà qualunque di una delle classi rese o dei componenti:
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
Nota: potete usare i costrutti elements e indices in una clausola select, anche su database senza sub-select.
È consentita anche la clausola having.
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)
Le funzioni SQL e le funzioni aggregate sono consentite nelle clausole having e order by, se supportate dal database sottostante (ad esempio non in MySQL).
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
Notate che né la clausola group by né la order by possono contenere espressioni aritmetiche.
Per i database che supportano i sub-select, Hibernate supporta le sottointerrogazioni all'interno delle interrogazioni. Una sottointerrogazione deve essere circondata da parentesi (spesso da una chiamata di funzione aggregata SQL). Sono permesse anche le sottointerrogazioni correlate (ovvero quelle che fanno riferimento ad un sinonimo nella interrogazione esterna).
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 )
Le interrogazioni di Hibernate possono essere abbastanza potenti e complesse. In effetti, il potere del linguaggio di interrogazione è uno dei principali punti di forza di Hibernate. Qui presentiamo alcuni esempi di interrogazioni molto simili a query che sono state usate in un recente procetto. Notate che molte delle interrogazioni che scriverete sono molto più semplici di queste!
La prossima interrogazione restituisce l'id dell'ordine, il numero di oggetti e il valore totale dell'ordine per tutti gli ordini non pagati per un cliente particolare e un valore totale minimo, ordinando i risultati per valore totale. Nella determinazione dei prezzi, utilizza il catalogo corrente. La query SQL risultante, ha quattro join interni e una subselect non correlata che insistono sulle tabelle ORDER, ORDER_LINE, PRODUCT, CATALOG e PRICE.
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
Che mostro! A dire il vero, nella vita reale non ho molta passione per le sottointerrogazioni, quindi la mia era più come la seguente:
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 prossima interrogazione conta il numero di pagamenti in ogni stato, escludendo tutti i pagamenti nello stato AWAITING_APPROVAL quando il cambiamento di stato più recente era stato fatto dall'utente corrente. Si traduce in una query SLQ con due join interni e una subselect correlata sulle tabelle PAYMENT, PAYMENT_STATUS e 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
Se avessi mappato la collezione statusChanges come una lista invece di un set, l'interrogazione sarebbe stata molto più semplice da scrivere.
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 prossima interrogazione usa la funzione isNull() di MS SQL Server per restituire tutti i conti e i pagamenti non effettuati per l'organizzazione a cui l'utente corrente appartiene. Si traduce in una query SQL con tre join interni, un join esterno e una subselect sulle tabelle ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATION e 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
Per alcuni database, avremmo bisogno di fare a meno della subselect correlata.
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
Potete contare il numero dei risultati di una interrogazione senza restituirli veramente:
( (Integer) session.iterate("select count(*) from ....").next() ).intValue()
Per ordinare un risultato per dimensione di una collezione, usate l'interrogazione seguente:
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)
Se il vostro database supporta le sottointerrogazioni, potete mettere una condizione sulla dimensione della selezione nella clausola where della vostra query:
from User usr where size(usr.messages) >= 1
Mentre se il database non supporta i subselect potete usare la query seguente:
select usr.id, usr.name from User usr.name join usr.messages msg group by usr.id, usr.name having count(msg) >= 1
Poiché questa soluzione non può restituire uno User con zero messaggi a causa del join interno, è anche utile la forma seguente:
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
Le proprietà di un javabean possono essere assegnate a parametri della query con nome:
Query q = s.createQuery("from foo in class Foo where foo.name=:name and foo.size=:size"); q.setProperties(fooBean); // fooBean has getName() and getSize() List foos = q.list();
Le collezioni sono paginabili usando l'interfaccia Query con un filtro:
Query q = s.createFilter( collection, "" ); // the trivial filter q.setMaxResults(PAGE_SIZE); q.setFirstResult(PAGE_SIZE * pageNumber); List page = q.list();
Gli elementi delle collezioni possono essere ordinati o raggruppati usando un filtro di interrogazione:
Collection orderedCollection = s.filter( collection, "order by this.amount" ); Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" );
Potete individuare la dimensione di una collezione senza inizializzarla:
( (Integer) session.iterate("select count(*) from ....").next() ).intValue();