Java Persistence Query Language (JPQL)
Il metodo find dell’interfaccia EntityManager consente di recuperare un’entità di cui si conosce esattamente la primary key, le Query API invece consentono di scrivere query che consentono di recuperare collezioni di entità.JPA Query API consente di usare Java Persistence Query Language (JPQL) o SQL, la differenza fra le due soluzione sta nel fatto che JPQL restituisce entità mentre SQL restituisce record.
Le Query API supportano due tipi di query: named e dynamic.
Le named query sono tipicamente utilizzate per eseguire le operazioni più comuni, vengono infatti memorizzate e riutilizzate quando è necessario.
Le dynamic query invece create secondo le necessita applicative del momento.
Se ad esempio supponiamo di voler recuperare tutte le categorie presenti nel sistema possiamo scrivere:
ext EntityManager manager; ... public List recuperaCategorie() { Query query = manager.createQuery("SELECT c FROM Categoria c"); return query.getResultList(); }Come vediamo, dapprima si ottiene l’accesso, mediante dependency injection, all’EntityManager.
Quindi viene creata un’istanza dell’oggetto Query mediante il metodo createQuery dell’EntityManager passando la query string.
Infine si ottiene il risultato mediante il metodo getResultList.
Una named query è simile, la differenza è che si usa il metodo createNamedQuery al posto di createQuery passando un query object che è stato creato in precedenza.
Named Query
Per poter usare una named query occore dapprima crearla, per fare ciò si possono usare le annotazioni o il deployment descriptor che definisce il mapping object-relational (file orm.xml).Una named query ha un nome che la identifica univocamente ed in linea teorica qualsiasi query che viene utilizzata da più componenti dell’applicazione è candidata ad essere una named query.
L’utilizzo di named query comporta essenzialmente tre benefici:
- migliora la riusabilità delle query
- facilita il mantenimento del codice
- incrementa le performance del sistema
Per ottenere ciò usiamo l’annotazione .persistence.NamedQuery
(name="trovaCategorie", query="SELECT c FROM Categoria c WHERE c.nome LIKE :nomeCategoria ") public class Categoria implements Serializable { ... }Il precedente esempio definisce una named query di nome trovaCategorie.
Nel caso di applicazioni complesse è possibile avere più named query: in questo caso si usa l’annotazione .persistence.NamedQueries che contiene al suo interno le .
Dal momento che le named query hanno scope globale, è possibile creare un’istanza di una named query da qualsiasi componente che abbia accesso alla persistence unit alla quale l’entità appartiene.
Per creare una named query è necessario invocare il metodo createNamedQuery dell’EntityManager passando il nome della query desiderata come parametro.
Query query = manager.createNamedQuery("trovaCategorie");L’istanza dell’EntityManager si occupa quindi di tutti i dettagli relativi al fetching della named query e restituiendo alla fine un riferimento alla Query.
L'interfaccia Query
L’interfaccia Query definisce diversi metodi per:- eseguire una query
- settare i parametri dell’istanza Query
- definire le proprietà di paginazione dei risultati
- controllare la modalità di flush
- getResultList(): restituisce l’insiem dei risultati prodotti da una query sottoforma di un oggetto List
- getSingleResult(): restituisce un solo risultato sottoforma di un Object
- executeUpdate(): esegue un’istruzione UPDATE o DELETE
- setMaxResults(int max): imposta il numero massimo di oggetti recuperati
- setFirstResult(int pos): imposta la posizione del primo oggetto recuperato dalla query
- setHint(String hintname, Object value): imposta una caratteristica specifica del produttore per la query
- setParameter(String name, Object value): consente di impostare un parametro della query
- setParameter(String name, Date value, TemporalType temporalType): consente di impostare il valore di un parametro di tipo Date
- setParameter(String name, Calendar value, TemporalType temporalType): consente di impostare il valore di un parametro di tipo Calendar
- setParameter(int pos, Object value): consente di impostare il valore di un parametro posizionale
- setFlushMode(FlushModeType flushmode): consente di impostare la modalità di flush che determina in che modo l’EntityManager effettua gli aggiornamenti sul database
Passaggio dei parametri
Come è facile osservare, esistono due modi mediante i quali è possibile specificare parametri: per numero o per nome.Se usiamo il numero allora il parametro è detto posizionale e prima dell’esecuzione della query dobbiamo indicare il parametro mediante il metodo setParameter passando la posizione e il valore del parametro:
query.setParameter(1, 20);I parametri posizionali all’interno della query vanno indicati con il punto interrogativo seguito dal numero (ad esempio ?1).
Se invece indichiamo i parametri per nome allora parliamo di parametri nominali e otteniamo una migliore leggibilità del codice, in questo caso il metodo setParameter riceve come argomenti il nome del parametro e il valore:
query.setParameter("costo", 10);I parametri nominali all’interno della query vanno indicati con i due punti seguiti dal nome del parametro (ad esempio :costo).
Paginazione dei risultati
Una singola query potrebbe produrre come risultato centinaia o migliaia di entità.JPA fornisce la possibilità di paginare i rusultati ottenuti mediante i metodi setMaxResults e setFirstResult come mostra il seguente codice:
query.setMaxResults(50); query.setFirstResult(0); List risultato = query.getResultList();Il metodo setMaxResults consente di specificare il massimo numero di entità che devono essere recuperate, mentre setFirstResult consente di specificae la posizione del primo rusultato nel risultato.
Il precedente codice ad esempio restituiva le prime 50 entità recuperate, il seguente restituisce le successive 50:
query.setMaxResults(50); query.setFirstResult(50); List risultato = query.getResultList();
SELECT, DELETE, UPDATE
JPQL supporta tre tipi di iscruzioni: SELECT, UPDATE e DELETE.L’istruzione SELECT viene utilizzata per recuperare entità dal database, il formato tipico è il seguente:
SELECT c FROM Categoria c WHERE c.nome LIKE :nomeCategoria ORDER BY c.datadove FROM specifica il tipo di entità da recuperare, WHERE consente di filtrare i risultati secondo qualche criterio, ORDER BY ordina i risultati ottenuti.
Possono essere presenti anche:
- GROUP BY che aggrega i risultati ottenuti
- HAVING che consente di filtrare i risultati derivanti l’aggregazione.
E’ possibile definire il nome dell’entità utilizzando l’attributo name dell’annotazione e se questo non viene specificato si assume come nome il nome della classe.
Il nome dell’entità naturalmente deve essere unico all’interno della persistence unit.
E’ possibile anche fare uso di variabili identificatrici definite nel seguente modo:
FROM nomeentità [AS] variabileidentificatricedove le [] sono opzionali.
Il nome della variabile naturalmente non può essere una parola riservata, né il nome di un’altra entità nella stessa persistence unit.
L’istruzione UPDATE viene utilizzata per aggiornare le entità di un certo tipo:
UPDATE Categoria c SET nome = “libri”, ... WHERE c.nome = “lettura”La clausola SET viene utilizzata per indicare i valori dei campi persistenti da modificare e il nuovo valore a questi associato.
La clausola WHERE ha esattamente la stessa funzione vista nel caso dell’istruzione SELECT.
L’istruzione DELETE consente di eliminare le entità di un certo tipo che soddisfano le condizioni definite dalla clausola WHERE
DELETE Categoria c WHERE c.nome = “prova”
Path expression
Una path expression è una variabile identificatrice seguita dall’operatore di navigazione "." e da un campo persistente o un campo associazione, quest’ultimo può contenere un singolo oggetto o una collezione.Il campo associazione che rappresenta associazioni uno a molto o molto a molti è tipo collection.
Per esempio se si ha una relazione molti a molti fra Associazione e Persona:
SELECT distinct p FROM Persona p WHERE p.associazioni is NOT EMPTYIn questo caso p.associazioni è un tipo collection quindi l’espressione è detta collection-value expression.
Se l’associazione fosse stata uno a uno o molti a uno allora l’espressione sarebbe stata una single-value path expression.
E’ possibile navigare attraverso campi persistenti o associazioni usando single-value path expression.
p.info.info1Non è possibile usare path expression per navigare attraverso tipi collection.
Espressioni condizionali
Una condizione espressa nella clausola WHERE che filtra i risultati della query è detta espressione condizionale.E’ possibile costruire espressioni condizionali usanto path expression e operatori supportati dal linguaggio.
JPQL può valutare un’espressione con numeri, stringhe, booleani usanto operatori relazionali.
Ecco un esempio di espressione condizionale:
c.nome = 'libri'E’ possibile usare l’operatore BETWEEN in espressioni aritmetiche per confrontare una variabile con un range di valori.
L’operatore IN invece permette di verificare se il valore di una path expression appartiene o meno ad una lista di valori
path_expression [NOT] IN (List_of_values)L’operatore LIKE consente di determinare se una single-value path expression corrisponde ad una pattern string.
La sintassi dell’operatore è la seguente:
string_value_path_expression [NOT] LIKE pattern_valueIl pattern_value può contenere i valori _ e %: _ sta per un singolo carattere mentre % rappresenta un numero qualsiasi di caratteri.
E’ possibile usare IS NULL o IS NOT NULL per stabilire se una single-value path expression contiene valori null o non null.
WHERE c.campo IS NOT NULLNel caso di un collection type, non è possibile usare IS NULL ma JPQL fornisce gli operatori IS EMPTY o IS NOT EMPTY.
WHERE c.articoli IS EMPTYE’ possibile usare l’operatore MEMBER OF per testare se una variabile identificatore, una single-value path expression o un parametro di input appartiene ad una collection-value path expression.
Questa è la sintassi dell’operatore MEMBER OF
entity_expression [NOT] MEMBER [OF] collection_value_path_expression
Funzioni di aggregazione
Le aggregazioni sono utili quando si scrivono query con l’obiettivo di collezionare entità.JPQL fornisce le funzioni di aggregazione AVG, COUNT, MAX e MIN che possono essere usate solo su campi persistenti.
Se vogliamo ad esempio vogliamo trovare il valore massimo per il campo prezzo fra tutti gli articoli useremo:
SELECT MAX(a.prezzo) FROM Articolo aSe vogliamo conoscere il numero di categorie:
SELECT COUNT(c) FROM Categoria c
GROUP BY e HAVING
In una applicazione si potrebbe avere la necessità di raggruppare i dati in base a qualche dato persistente usando la clausola GROUP BY.Assumendo che esista una relazione uno a molti tra Articolo e Categoria, la seguente query genererà un report del numero di categorie create da ogni utente:
SELECT a.categoria, COUNT(a.id) FROM Articolo a GROUP BY a.categoriaE’ possibile raggruppare per single value path expression che sono persistenti o campi associazione, inoltre quando si usa GROUP BY si possono usare solo funzioni di aggregazione.
E’ possibile inoltre filtrare ulteriormente i risutati mediante la clausola HAVING
Supponiamo ad esempio di voler recuperare gli Utenti che hanno creato più di 5 categorie, scriveremo:
SELECT c.utente, COUNT(c.id) FROM Categoria c GROUP BY c.utente HAVING COUNT(c.id) > 5
ORDER BY
E’ possibile controllare l’ordinamento dei valori mediante la clausola ORDER BYORDER BY path_expression1 [ASC | DESC], ... path_expressionN [ASC | DESC]Se ad esempio vogliamo recuperare tutte le entità Categoria e ordinarle alfabeticamente per nome
SELECT c FROM Categoria c ORDER BY c.nome ASCSpecificando ASC stiamo indicando che vogliamo i risultati ordinati in ordine ascendente (DESC specifica l’opposto).
E’ possibile utilizzare più campi per effettuare l’ordinamento a parità di valore:
SELECT c FROM Categoria c ORDER BY c.nome ASC, c.titolo DESCLa clausola SELECT deve contenere le path expression usate dalla clausola ORDER BY.
Sottoquery
Una sottoquery è una query dentro una query, è possibile usare sottoquery con le clausole WHERE o HAVING per filtrare i risultati ma diversamente da SQL non è possibile usarle nella clausola FROM.Se si ha una sottoquery questa viene valutata per prima e quindi la query principale viene valutata su questa.
Ecco la sintassi di una sottoquery:
[NOT] IN / [NOT] EXISTS / ALL / ANY / SOME (subquery)IN si usa per valutare se una single-value path expression appartiene ad una lista di valori
SELECT a FROM Articolo a WHERE a.utente IN (SELECT c.utente FROM Categoria c WHERE c.nome LIKE :nome)EXISTS verifica se la sottoquery contiene qualche valore restituendo true o false altrimenti.
SELECT a FROM Articolo a WHERE EXISTS (SELECT c FROM Categoria c WHERE c.utente = a.utente)ANY, ALL e SOME sono simili all’operatore IN e possono essere usati con qualsiasi operatore di confronto numerico come =, >,>=, <, <=, <>
SELECT c FROM Categoria c WHERE c.data >= ALL (SELECT a.data FROM Articolo a WHERE a.utente = c.utente)L’operatore ALL produce true se tutti i risultati prodotti dalla sottoquery soddisfano la condizione data.
SOME e ANY sono equivalenti restituiscono true se almeno un risultato recuperato dalla sottoquery soddisfa la condizione.
Operazioni di JOIN
E’ possibile usare la clausola JOIN per creare il prodotto cartesiano fra due entità specificando nella clausola FROM le entità coinvolte.Il prodotto cartesiano viene fatto sulla base delle relazioni fra le entità o di qualche campo persistente.
Per esempio se supponiamo di voler effettuare il join di Categoria e Articolo usando la relazione fra queste pee recuperare soltanto le entità che soddisfano la relazione possiamo definire un INNER JOIN.
[INNER] JOIN join_association_path_expression [AS] identification_variableDiversamente se supponiamo di voler recuperare i risultati che soddisfano la condizione JOIN ma che includono anche le entità da una parte del dominio che non hanno corrispondenze con l’altra parte possiamo definire un OUTER JOIN.
LEFT OUTER JOIN join_association_path_expression [AS] identification_variableIn alcune applicazioni potrebbe essere utile recuperare alcune entità rispetto alle entità a queste associate.
Per fare ciò possiamo usare il FETCH JOIN:
SELECT u FROM Utente u FETCH JOIN u.info WHERE u.nome = :nome
The Java Persistence Query Language
Tutorial Sun su Java Persistence Query Language (JPQL)
Tutorial Sun su Java Persistence Query Language (JPQL)
Querying JPA Entities with JPQL and Native SQL
Esempio dell'utilizzo di Java Persistence Query Language (JPQL) e di SQL nativo
Esempio dell'utilizzo di Java Persistence Query Language (JPQL) e di SQL nativo