5. Consultes
1. Hibernate Query Language
La documentació completa es pot trobar aquí:
El llenguatge HQL (Hibernate Query Language) va néixer amb l'objectiu de preservar el model relacional, ja que és un superset de SQL (extensió de SQL que inclou SQL). La primera consideració és que, per defecte, la seva funcionalitat és recuperar objectes de la base de dades, no taules com fèiem en el llenguatge SQL a través de ResultSet. Les consultes amb HQL es faran des d'una interfície Query, que serà el lloc on especificarem què volem obtenir. Opcionalment, podem afegir a la consulta els paràmetres necessaris per a la seva execució, per evitar consultes amb codi dur.
En primer lloc, la consulta serà preparada. En crear-la, hem de tenir en compte dues opcions, amb quatre combinacions possibles:
- Retornarà un resultat o més d'un i,
- Retornarà el resultat encapsulat en objectes o de manera composta.
Depenent de la resposta obtinguda, en el segon pas triarem una de les quatre opcions vistes a continuació.
| Java | |
|---|---|
- In first option the query returns a collection of objects (several rows, on object in a row).
- This option return a collection with a composite of objects and another types: for example a Student a number of topics and number of teachers.
- Both last options is when, instead of a collection, the query returns a unique object/composite.
Apunt...
session.createQuery→ permet escriure una consulta HQL, amb o sense paràmetres.-
Query.list()→ obté tots els objectes (com una Llista) de la base de dades que satisfan la consulta. -
Query.uniqueResult()→ obté un únic objecte de la base de dades.
Abans dels detalls, aquí trobaràs algunes idees generals:
- Podem eliminar el
Select *, per indicar que volem tots els atributs. - Hi ha una sensibilitat mitjana a les majúscules i minúscules: les paraules reservades de HQL no distingeixen entre majúscules i minúscules, però les referències als objectes (els seus noms) sí que ho fan: no importa posar
FromofROm, però unStudentno és el mateix que unstudent. Es recomana escriure les sentències en minúscules, per tal de distingir-les de les classes. - Després de
from, apareix el nom d'una classe (o classes), no de taules. - Assignar àlies és altament recomanable. Podem utilitzar joins de manera similar a SQL.
- Després pot aparèixer el
where, amb les seves condicions similars a SQL. - Les constants de text es tanquen entre cometes simples.
- Podem consultar col·leccions que apareixen dins dels objectes (per exemple, el conjunt d'estudiants d'un professor).
- No hi ha operador
limit. order bys'utilitza de manera similar a SQL.group byestà permès, i les funcions agregades són: COUNT, AVG, SUM, MIN i MAX.
2. Recuperant objectes
A partir d'ara, els exemples es basen en aquest esquema Docencia

Consell
Ara és un bon moment per intentar fer un enfocament d'enginyeria inversa per construir el teu projecte a partir de la base de dades.
3. Consultes
3.1. Obtenint objectes simples
Aquestes consultes són les que volem recuperar un objecte o una col·lecció d'aquests de la base de dades (una o més files, en comparació amb ResultSet). Cada resultat és un únic objecte d'una taula de la base de dades. Es caracteritzaran perquè la paraula Select pot no aparèixer o simplement indicar l'àlies de la classe que tenim. Vegem els exemples.
El següent exemple mostra tots els estudiants:
Si volem filtrar o cercar qualsevol estudiant, necessitem utilitzar la clàusula where, com en una consulta SQL normal. Les expressions poden ser:

Quan el filtre where només retorna un objecte, podríem emmagatzemar-lo en una llista i després accedir-hi, però és millor obtenir l'objecte directament, com segueix:
| Java | |
|---|---|
uniqueResult:
| Java | |
|---|---|
Atenció
Tingueu cura quan recupereu un objecte únic i la consulta en retorna diversos.
Quan tenim molts resultats, no és convenient recuperar-los tots alhora, sinó accedir-hi de 10 en 10 o similar, igual que les pàgines de cerca de Google o Amazon. Podem aconseguir-ho llançant consultes més petites repetidament, aplicant a la consulta:
Q.setFirstResult(int start)→ indica el primer objecte (fila) a retornar.Q.setMaxResult(int howMany)→ indica quantes files retornarà.
Amb un algorisme dissenyat adequadament, podem fer un bucle, desplaçant l'inici en cada iteració, incrementant-lo pel nombre de files recuperades en l'iteració anterior. Això implicaria moltes consultes petites en lloc d'una de gran.
3.2. Obtenint objectes compostos
Si necessitem obtenir més (o menys) dades que un objecte sencer, ho hem de fer a la part select de la clàusula, però quan obtenim les dades no les podem emmagatzemar en un Object. Per aquesta raó, i utilitzant la propietat de polimorfisme de Java, obtenim els resultats en un array genèric d'objectes. Per tant, hem de ser molt curosos amb el tipus de cada cel·la, així com la mida d'aquest array, ja que estarà fortament lligat a la consulta en si. Vegem la següent consulta: Mostra el nom i l'edat dels estudiants:
| Java | |
|---|---|
Tingues en compte que si només vols imprimir la informació, no cal fer cap treball extra, ja que (gairebé) tots els objectes es poden convertir a String. Si vols utilitzar-los d'una altra manera, tin molt de compte en convertir-los al tipus de dada adequat.
Col·leccions
Anem a comprovar el nom dels estudiants i quants exàmens han fet. Aquesta informació es troba al conjunt d'Exàmens, així que necessitarem manipular aquesta col·lecció:
| Java | |
|---|---|
Com podeu veure, hem aplicat la funció size() a la col·lecció per veure la seva mida. Per tant, podem aplicar:
size(collection)→ recuperar la mida de la col·lecció.collection is empty|collection is not empty→ per determinar si està buida. És equivalent a comparar la mida amb 0.- Els operadors
in,alles poden combinar utilitzant l'operadorelements(collection).
Com a consulta avançada, podeu utilitzar exists i not exists en les vostres consultes.
4. Paràmetres i Consultes Nominals (amb nom)
Normalment, la majoria de consultes necessitaran alguns paràmetres, generalment per al filtratge d'objectes en la clàusula where. Els beneficis de parametritzar les consultes per evitar la injecció de SQL o problemes similars ja es van discutir en la unitat anterior.
La gestió dels paràmetres es realitza de la mateixa manera que amb les sentències preparades (preparedStatements), i es pot fer mitjançant paràmetres posicionals o nominals. Vegem alguns exemples:
4.1. Paràmetres posicionals
Hem d'afegir espais reservats dins de la nostra consulta, però en lloc de ? afegim un número després del signe d'interrogació. Després hem de configurar el paràmetre segons el número d'espai reservat que hem establert abans, de la següent manera:
| Java | |
|---|---|
Consell
Tingues en compte que el mètode setParameter està sobrecarregat per establir tot tipus de dades.
4.2. Paràmetres nominals
Les posicions estan bé, però si podem indicar els paràmetres de manera nominal, el programa serà molt més llegible. Els paràmetres s'indiquen amb :nomDelParametre i s'assignaran amb el mètode setParameter(nomDelParametre, valor), indicant el nom del paràmetre (sense els dos punts):
4.3. Consultes amb nom
Podem escriure consultes en els nostres mètodes segons les necessitem. No obstant això, és una bona pràctica crear les consultes més importants o que preveiem que seran les més utilitzades juntament amb la mateixa classe, a través del mecanisme que es presenta a continuació. En aquesta secció creem i etiquetem les nostres consultes, dins d'una col·lecció de consultes, i després les podem cridar. És com si creéssim una biblioteca de consultes.
Fora de la definició de la classe, es crearà una col·lecció @NamedQueries, que contindrà un array (indicat per claus) d'elements @NamedQuery, cadascun d'ells amb un nom i una definició de consulta.
Per invocar-les, en lloc de crear un objecte Query, el crearem a través d'una NamedQuery, indicant el seu nom i assignant paràmetres, si n'hi ha.
A la classe Alumno:
Quan volem utilitzar-lo, en lloc de crear una Query hem de crear una NamedQuery, de la següent manera:
5. CRUD
Finalment, analitzarem la resta de les operacions CRUD. Cal destacar que aquestes operacions es poden realitzar directament sobre els objectes, per això s'expliquen com a complementàries més que principals.
5.1. Crear (Create - Insert)
Si volem inserir un nou objecte a la base de dades, és molt fàcil, com podem recordar en les primeres seccions d'aquesta unitat.
| Java | |
|---|---|
Simplement creem un nou objecte, establim els seus valors, mitjançant setters o el constructor amb tots els arguments, i finalment l'emmagatzemem a la base de dades amb el mètode persist. Podeu utilitzar save o saveOrUpdate indistintament. Podeu veure algunes diferències entre ells aquí. Tots dos executen una instrucció insert sql.
De la mateixa manera que SQL, podríem inserir en una taula amb el resultat d'una consulta, com segueix:
| SQL | |
|---|---|
5.2. Actualització (Update)
Per canviar el valor d'una o més propietats d'un objecte, podem carregar l'objecte de la base de dades, canviar els valors mitjançant els mètodes getters i després desar els canvis.
| Java | |
|---|---|
Si volem establir una actualització massiva, que afecti diversos registres a la base de dades, podríem utilitzar-ho de la mateixa manera que en SQL:
| Java | |
|---|---|
5.3. Eliminació (Delete)
Com en les seccions anteriors, podríem eliminar un únic objecte o un conjunt de registres que compleixin una condició. En el cas d'un únic objecte, necessitem carregar-lo i després eliminar-lo:
Tingues en compte que l'objecte roman en memòria o fins al final del mètode, però quan la sessió es confirma, l'objecte passarà a estar detached. L'estat detached significa que aquest objecte està fora de qualsevol tipus de seguiment. No tenim la possibilitat de desar o actualitzar aquest objecte de nou.
Per fer una eliminació massiva, de la mateixa manera que SQL:
| Java | |
|---|---|
Consell
Recorda aquests consell generals:
- Aquestes sentències poden contenir paràmetres (en sentències que contenen la clàusula
where). - El
whereés opcional, però eliminarà o actualitzarà tot si falta. - Aquestes consultes s'executen totes utilitzant
executeUpdate(), perquè canvien l'estat de la base de dades, i retornarà un enter amb el nombre de files afectades.
Has de prestar atenció a les eliminacions, ja que depenent de com hem definit les relacions (eliminacions en cascada, deixant files orfes, etc.) poden o no ocórrer, i llançar java.sql.SQLIntegrityConstraintViolationException.
Com a comentari final en aquesta secció, recorda el que s'ha dit anteriorment: en la manipulació d'objectes (eliminacions, modificacions, etc.) tenim prou eines per fer-ho sense consultes HQL. Aquestes consultes són més adequades per processar grans volums d'informació sense carregar aquesta informació al nostre programa per processar-la.
6. Apèndix, Estat dels objectes
Com hem dit al llarg d'aquesta unitat, els objectes es carreguen i es controlen pel gestor de sessions. Cada objecte podria tenir un estat dins de la sessió, i és interessant conèixer totes les possibilitats. En aquesta imatge, podeu veure tots els diferents estats i el mètode per canviar l'estat de l'objecte.

- transient → L'objecte té identitat (existeix en memòria), però no té una identitat persistent, o clau primària.
- persistent → L'objecte ha estat marcat per ser desat (persistit), i la identitat en memòria està relacionada amb la identitat de la base de dades. Quan carreguem un objecte de la base de dades, aquest és l'estat inicial.
- detached → L'objecte ha estat persistit recentment, però aquest context està tancat, amb el mètode
detach(). Les operacions quan està desconnectat no es reflectiran a la base de dades. - removed → L'objecte ha estat marcat per ser eliminat (però encara no s'ha efectuat). Quan la sessió es tanqui, serà eliminat efectivament, però podem tornar-lo a carregar i posar-lo en un context persistent.
Podeu obtenir més informació aquí.