JJ — n. 5 — novembre-febbraio 2008 JPA: Java Persistence API di Fabio Staro parte 2 La specifica JPA fornisce uno stan...
JJ — n. 5 — novembre-febbraio 2008
JPA: Java Persistence API
parte 2
di Fabio Staro La specifica JPA fornisce uno standard per la persistenza dei dati in Java, semplificando ` radicalmente la precedente tecnologia degli EJB di tipo Entity Bean. Tuttavia JPA non e ` di fatto una nuova architettura una reingegnerizzazione della precedente tecnologia ma e
Fabio Staro ` ReDottore in Fisica e sponsabile Tecnico per i progetti Java presso la Direzione Ricerca ed Innovazione di Engineering Ingegneria Informatica S.p.A.
pubblicato su WWW.INFOMEDIA.IT ! stampa digitale da Lulu Enterprises Inc. stores.lulu.com/infomedia Infomedia Infomedia e` l’impresa editoriale che da quasi venti anni ha raccolto la voce dei programmatori, dei sistemisti, dei professionisti, degli studenti, dei ricercatori e dei professori d’informatica italiani. Sono pi`u di 800 gli autori che hanno realizzato per le testate Computer Programming, Dev, Login, Visual Basic Journal e Java Journal, molte migliaia di articoli tecnici, presentazioni di prodotti, tecnologie, protocolli, strumenti di lavoro, tecniche di sviluppo e semplici trucchi e stratagemmi. Oltre 6 milioni di copie distribuite, trentamila pagine stampate, fanno di questa impresa la pi`u grande ed influente realt`a dell’editoria specializzata nel campo della programmazione e della sistemistica. In tutti questi anni le riviste Infomedia hanno vissuto della passione di quanti vedono nella programmazione non solo la propria professione ma un’attivit`a vitale e un vero divertimento. Nel 2009, Infomedia e` cambiata radicalmente adottando un nuovo modello aziendale ed editoriale e si e` organizzata attorno ad una idea di Impresa Sociale di Comunit`a, partecipata da programmatori e sistemisti, separando le attivit`a di gestione dell’informazione gestite da un board comunitario professionale e quelle di produzione gestite da una impresa strumentale. Questo assetto e` in linea con le migliori esperienze internazionali e rende Infomedia ancora di pi`u parte della Comunit`a nazionale degli sviluppatori di software. Infomedia e` media-partner di manifestazioni ed eventi in ambito informatico, collabora con molti dei pi`u importanti editori informatici italiani come partner editoriale e fornitore di servizi di localizzazione in italiano di testi in lingua inglese.
L’impaginazione automatica di questa rivista e` realizzata al 100% con strumenti Open Source usando OpenOffice, Emacs, BHL, LaTeX, Gimp, Inkscape e i linguaggi Lisp, Python e BASH
For copyright information about the contents of Java Journal, please see the section “Copyright” at the end of each article if exists, otherwise ask authors. Infomedia contents is © 2008 Infomedia and released as Creative Commons 2.5 BY-NC-ND. Turing Club content is © 2008 Turing Club released as Creative Commons 2.5 BY-ND. Le informazioni di copyright sul contenuto di Java Journal sono riportate nella sezione “Copyright” alla fine di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e` © 2008 Infomedia e rilasciato con Licenza Creative Commons 2.5 BY-NC-ND. Il contenuto Turing Club e` © 2008 Turing Club e rilasciato con Licenza Creative Commons 2.5 BY-ND. Si applicano tutte le norme di tutela dei marchi e dei segni distintivi. E` in ogni caso ammessa la riproduzione parziale o totale dei testi e delle immagini per scopo didattico purch´e vengano integralmente citati gli autori e la completa identificazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. Contenuto pubblicitario inferiore al 45%. La biografia dell’autore riportata nell’articolo e sul sito www.infomedia.it e` di norma quella disponibile nella stampa dell’articolo o aggiornata a cura dell’autore stesso. Per aggiornarla scrivere a
[email protected] o farlo in autonomia all’indirizzo http://mags.programmers.net/moduli/biografia
JAVA Journal
focus
JPA: Java Persistence API seconda parte La specifica JPA fornisce uno standard per la persistenza dei dati in Java, semplificando radicalmente la precedente tecnologia degli EJB di tipo Entity Bean. Tuttavia JPA non è una reingegnerizzazione della precedente tecnologia ma è di fatto una nuova architettura
>> di Fabio Staro (
[email protected])
N
el precedente articolo su JPA (acronimo di Java Persistence Api) [1] abbiamo introdotto le caratteristiche base della specifica ed abbiamo approfondito la gestione delle relazioni tra le entità. Nell’articolo risaltava in modo prepotente la radicale semplificazione presente con JPA rispetto alla precedente specifica per la persistenza dei dati in Java: gli Entity Bean versione 2.x. In questo articolo, conclusivo sull’argomento, approfondiamo ulteriormente la specifica soffermandoci sulle strategie di mapping per la gestione della ereditarietà, un design pattern frequente nel paradigma di sviluppo object-oriented e non presente nel mondo relazionale dei database. Gli esempi riportati realizzano un processo di sviluppo top-down: ossia, partendo dal modello delle classi deriveremo il disegno delle tabelle. Gli esempi sono stati verificati con l’application server JBoss versione 5.0 [2], con il database MySQL versione 5.0.27 [3] e con il provider JPA Hibernate [4].
Strategie per l’ereditarietà Gli esempi che desideriamo realizzare sono volutamente semplificati, al fine di approfondire l’API e l’architettura JPA eliminando le complessità applicative legate ad un particolare dominio di interesse. Osservando il diagramma delle classi riportato in Figura 1 possiamo notare la semplice gerarchia presente tra le classi: Prodotto, Libro, Video, DVD e VHS. In particolare, la classe astratta Prodotto è la root class della gerarchia e le classi Libro e Video la estendono direttamente. La classe Video è a sua volta astratta ed è estesa dalle classi concrete DVD e VHS.
38
n.5 - novembre/febbraio 2008
Tra le classi Prodotto e Autore è presente una relazione molti a molti bidirezionale. E tra le classi Autore ed Indirizzo è presente una relazione uno a uno unidirezionale. La specifica JPA, per supportare in un database relazionale il concetto di ereditarietà, offre varie strategie di mapping. Queste sono specificate nella enumerazione javax.persistence.InheritanceType: • • •
SINGLE_TABLE; JOINED; TABLE_PER_CLASS;
In questo articolo analizzeremo le prime due strategie per il mapping delle entità tralasciando la strategia TABLE_PER_CLASS in quanto il suo supporto da parte dei provider JPA non è obbligatorio nella attuale versione della specifica.
La strategia SINGLE_TABLE Iniziamo con l’approfondire la strategia di mapping SINGLE_TABLE. Questa è la strategia di default, dove una sola tabella è usata per rappresentare l’intera gerarchia delle classi. Una colonna della tabella, assumendo valori differenti in base al tipo Java rappresentato dal record, è usata come discriminante per distinguere tra le sottoclassi della gerarchia. Di seguito riportiamo uno stralcio del codice della classe Prodotto:
@Entity(name=”Prodotto_Single”) @Table(name=”PRODOTTO_SINGLE”) @Inheritance(strategy=InheritanceType.SINGLE _TABLE)
JAVA Journal
FIGURA 1
focus
Il diagramma delle classi
@DiscriminatorColumn(name=”DISC”, discriminatorType=DiscriminatorType.STRING) public abstract class Prodotto { ... @Id @GeneratedValue(strategy=GenerationType.AUTO) private int id; private String titolo; private Date annoProduzione; @ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.LAZY, targetEntity=Autore.class) private Set autori = null; ... }
Osservando il codice della classe Prodotto è possibile notare l’uso delle annotazioni @Inheritance e @DiscriminatorColumn. Attraverso l’annotazione @Inheritance è possibile specificare la strategia di mapping, nell’esempio SINGLE_TABLE. L’annotazione @DiscriminatorColumn consente di specificare il nome, nell’esempio “DISC”, e il tipo della colonna che funge da discriminante nel distinguere le sottoclassi della catena gerarchica. Di seguito riportiamo i frammenti di codice significativi per le altre classi della gerarchia. Iniziamo con uno stralcio del codice sorgente della classe Libro:
@DiscriminatorValue(“LIBRO”) public class Libro extends Prodotto { ... private int numeroPagine; private boolean edizioneEconomica; ... }
Ecco uno stralcio del codice della classe Video, @Entity(name=”Video_Single”) public abstract class Video extends Prodotto { ... private boolean colore; ... }
e della classe VHS, @Entity(name=”VHS_Single”) @DiscriminatorValue(“VHS”) public class VHS extends Video { ... private String tipoNastro = null; ... }
@Entity(name=”Libro_Single”)
n.5 - novembre/febbraio 2008
39
JAVA Journal
focus
java:/theDataSource org.hibernate.ejb.HibernatePersistence it.articolo.jpa2.Prodotto it.articolo.jpa2.Libro it.articolo.jpa2.Video it.articolo.jpa2.VHS it.articolo.jpa2.DVD it.articolo.jpa2.Autore it.articolo.jpa2.Indirizzo
LISTATO 1 Il file persistence.xml Osservando la Figura 2 possiamo notare come la strategia SINGLE_TABLE abbia effettivamente portato alla generazione di una singola tabella, nell’esempio la tabella PRODOTTO_SINGLE, le cui colonne sono tutti gli attributi delle classi costituenti la gerarchia, più la colonna discriminante dichiarata attraverso l’annotazione @DiscriminatorColumn, nell’esempio la colonna “DISC”. Osserviamo nel dettaglio la struttura della tabella PRODOTTO_SINGLE. Di seguito riportiamo il comando DDL (Data Defination Language) della tabella:
e, per concludere, della classe DVD: @Entity(name=”DVD_Single”) @DiscriminatorValue(“DVD”) public class DVD extends Video { ... private boolean contenutiSpeciali; ... }
Osservando il codice sopra riportato è possibile notare come, al variare delle classi, varia il valore della colonna “discriminante” (nell’esempio la colonna “DISC”). Il valore che assume in base al tipo della classe è specificato attraverso l’annotazione @DiscriminatorValue. In particolare, la colonna “DISC” assume il valore “LIBRO” in presenza di un record che rappresenta un oggetto della classe Libro e, viceversa, assume il valore “VHS” in presenza di un video del tipo VHS ed il valore “DVD” in presenza di un record che rappresenta un oggetto DVD. Dopo aver definito il modello ad oggetti e dopo aver dichiarato la strategia di mapping per l’ereditarietà, deriviamo la struttura delle tabelle del database (processo di sviluppo top-down). Il provider JPA che usiamo è Hibernate il quale grazie alla proprietà:
CREATE TABLE PRODOTTO_SINGLE ( DISC varchar(31) NOT NULL, id int(11) NOT NULL auto_increment, titolo varchar(255), annoProduzione datetime, numeroPagine int(11), edizioneEconomica bit(1), colore bit(1), tipoNastro varchar(255), contenutiSpeciali bit(1), PRIMARY KEY (id) )
hibernate.hbm2ddl.auto
impostata al valore “create” consente di derivare le tabelle del database partendo dalle classi opportunamente annotate. Il Listato 1 riporta il sorgente del file persistence.xml. Il risultato del deploy delle classi entity in JBoss è la creazione delle tabelle per il database MySql, riportate in Figura 2. FIGURA 2
40
n.5 - novembre/febbraio 2008
Il diagramma ER (strategia di mapping SINGLE_TABLE)
JAVA Journal
FIGURA 3
focus
Il diagramma ER (strategia di mapping JOINED)
A parte la colonna “DISC” e la colonna ID che è la chiave primaria, tutte le altre colonne della tabella possono assumere il valore NULL. Questo è necessario, in quanto un record della tabella può rappresentare una qualsiasi classe presente nella gerarchia. Se, per esempio, avessimo mappato l’attributo “contenutiSpeciali” della classe DVD su una colonna dichiarata come NOT NULL sarebbe stato impossibile persistere un oggetto delle classi Libro e VHS…
Considerazioni preliminari sulle strategie di mapping Quale strategia di mapping applicare ad una gerarchia di classi? La risposta a questa domanda non è semplice, in quanto varia in funzione del risultato che si desidera ottenere: per esempio, prestazioni adeguate o maggiore flessibilità durante le fasi di design e/o deployment. Di seguito cercheremo di delineare alcune linee guida, ben consci che tali indicazioni sono preliminari e che necessitano di una sperimentazione sul campo, specifica al proprio contesto.
La strategia JOINED Analizziamo ora la strategia di mapping di tipo JOINED. A livello di codice Java le classi Prodotto, Libro, Video, VHS e DVD non variano, a parte il valore assegnato alla annotazione @Inheritance. Di seguito uno stralcio del sorgente delle classe Prodotto e Libro: @Entity(name=”Prodotto_Joined”) @Table(name=”PRODOTTO_JOINED”) @Inheritance(strategy=InheritanceType.JOINED) public abstract class Prodotto {...} @Entity(name=”LIBRO_JOINED”) public class Libro extends Prodotto {...}
La struttura delle tabelle che deriva dal modello delle classi è riportata in Figura 3. Per ogni entità della gerarchia è generata una tabella le cui colonne sono solo gli attributi esplicitamente dichiarati nella classe. La root class della gerarchia è mappata su una root table (in Figura 3 la tabella PRODOTTO_JOINED) nella quale è definita la chiave primaria poi usata in tutte le tabelle della gerarchia. Ogni tabella nella gerarchia definisce una chiave primaria uguale alla chiave primaria della root table e definisce una foreign key (chiave esterna) verso la chiave primaria della root table.
Applicazione della strategia SINGLE_TABLE La strategia di mapping SINGLE_TABLE è consigliata in presenza di una gerarchia stabile e sostanzialmente semplice e poco “profonda”. Infatti aggiungere una nuova classe alla gerarchia o aggiungere nuovi attributi a qualche classe preesistente nella gerarchia porta alla creazione di nuove colonne nella tabella di mapping. Pertanto una gerarchia molto estesa può condurre alla generazione di una tabella con un numero di colonne elevato con la conseguenza di un impatto negativo sul “layout” della tabella e del database in generale. Sempre in relazione al “layout” della tabella di mapping è opportuno ricordare che le colonne della tabella mappate sugli attributi delle classi della gerarchia devono poter assumere il valore NULL. Inoltre, se per qualche motivo vi è la necessità di mappare uno o più attributi in colonne di tipo LOB può essere necessario mappare l’entity class non più su una sola tabella ma su più tabelle attraverso l’annotazione @SecondaryTable. Relativamente alle prestazioni, poiché generalmente si è in presenza di una singola tabella, le operazioni di ricerca sui diversi tipi della gerarchia o su uno specifico tipo risultano efficienti, non essendo richieste operazioni di JOIN tra tabelle.
n.5 - novembre/febbraio 2008
41
JAVA Journal
focus
Applicazione della strategia JOINED La strategia di mapping di tipo JOINED offre una buona flessibilità durante le fasi di design e di deployment. Infatti, l’aggiunta o la rimozione di uno o più attributi ad una classe presente nella gerarchia coinvolge nella modifica solo la tabella che specificatamente rappresenta la classe; mentre l’introduzione di una nuova classe nella gerarchia porta semplicemente alla creazione di una nuova tabella nello schema. Relativamente alle prestazioni, una operazione di ricerca tra le entità della gerarchia necessiterà di una o più operazioni di JOIN anche in funzione della “profondità” della gerarchia. Pertanto la presenza di un numero elevato di sottoclassi può avere un impatto negativo sulle prestazioni. Una API per le query con JPA La specifica JPA definisce un linguaggio per eseguire le query sulle entità ed il loro stato persistente. Il linguaggio consente allo sviluppatore di specificare la semantica di una query in modo portabile ed indipendente dallo specifico database presente nella propria infrastruttura tecnologica. Con JPA si è in presenza di una radicale estensione del linguaggio EJB QL definito nella precedente specifica per gli EJB di tipo Entity Bean versione 2.1 [5]. Nel precedente articolo su JPA [1] abbiamo osservato come attraverso l’oggetto EntityManager è possibile fare semplici operazioni di ricerca attraverso il metodo find(). La firma del metodo è: public primaryKey);
T
find(Class
entityClass,
Object
Il metodo accetta come parametro di input il tipo della entity class di cui tornarne una istanza ed un oggetto che rappresenta la chiave primaria e restituisce l’istanza della entity class di data chiave primaria. Il metodo è pratico e allo stesso tempo semplice. Tuttavia nei progetti è necessario, la maggior parte delle volte, eseguire query complesse la cui esecuzione restituisce, eseguendo operazioni di JOIN e condizioni di WHERE articolate, una o più istanze di entity class. In questi casi è necessario usare un oggetto di tipo Query, definito nel package javax.persistence e la sintassi estesa del linguaggio EJB QL. I passi essenziali per creare un oggetto Query ed eseguire una query sono schematizzati di seguito: • • •
Recuperare dall’Entity Manager una istanza della classe javax.persistence.Query; Impostare i parametri necessari all’esecuzione della query; Eseguire effettivamente la query.
Come si è detto in precedenza, le query possono essere scritte attraverso il linguaggio EJB QL. Tuttavia è anche possibile usare direttamente il linguaggio SQL ed in tal caso si è in presenza di query scritte in linguaggio nativo (indicate, infatti, con il termine native query nella specifica). Il linguaggio EJB QL, sintatticamente molto simile al linguaggio SQL, è la via preferenziale per scrivere le query
42
n.5 - novembre/febbraio 2008
in JPA. Infatti, il linguaggio è object-oriented, usando nella sua sintassi le entity class e le regole di navigazione definite tra le entità, ed è indipendente dal database. Al contrario, il linguaggio SQL, pur esistendo uno standard, è di fatto coniugato in diversi “dialetti” caratteristici di ogni database che non ne garantiscono la portabilità (vendor lock-in). Per ottenere una istanza dell’oggetto Query è possibile invocare sull’oggetto EntityManger o il metodo createQuery() o il metodo createNativeQuery(). Il primo metodo usa il linguaggio EJB QL per la definizione dello statement mentre il secondo metodo usa il linguaggio SQL. Per esempio, con riferimento alla Figura 2, per ottenere tutti i prodotti è possibile scrivere: public List getProdotti() { Query query = entityManager.createQuery(“SELECT o FROM Prodotto_Single o”); return query.getResultList(); }
L’espressione che rappresenta lo statement di una query può essere ricavata a runtime, ossia durante l’esecuzione del programma, come di fatto accade nell’esempio precedente; o può essere dichiarata in un file di configurazione della applicazione. Nel primo caso si è in presenza di una query dinamica, nel secondo di una query statica o di una named query. Le query statiche sono dichiarate attraverso la annotazione @NamedQuery. Per esempio, la query precedente può divenire una named query dichiarandola come annotazione del sorgente della classe Prodotto: @Entity(name=”Prodotto_Single”) ... @NamedQuery(name = “Prodotto.findAll”, query = “SELECT o FROM Prodotto_Single o”) public abstract class Prodotto implements Serializable { ... }
Per usare una named query il metodo precedente diviene: public List getProdotti() { Query query = entityManager.createNamedQuery( “Prodotto.findAll”); return query.getResultList(); }
Ovviamente è possibile dichiarare più di una named query come annotazione di una entity class. In tal caso è necessario usare l’annotazione @NamedQueries. Prima di terminare è opportuno accennare ai named parameters attraverso i quali è possibile specificare le condizioni di WHERE. Per esempio se vogliamo ricercare i prodotti che hanno un titolo particolare possiamo definire la named query
JAVA Journal
focus
come segue: @NamedQuery(name = “Prodotto.findByTitolo”, query = “SELECT o FROM Prodotto_Single o WHERE o.titolo = :TITOLO”)
ed un metodo che la usa: public List getProdottiByTitolo(String titolo) { Query query = entityManager.createNamedQuery( “Prodotto.findByTitolo”); query.setParameter(“TITOLO”, titolo); return query.getResultList(); }
@TransactionAttribute( TransactionAttributeType.REQUIRES_NEW) public void bulkUpdate(String entityName) { String dOperation = “UPDATE “+entityName+” t SET t.annoProduzione = :ANNO_PRODUZIONE”; Query q = entityManager.createQuery(dOperation); q.setParameter(“ANNO_PRODUZIONE”, null); q.executeUpdate(); } @TransactionAttribute( TransactionAttributeType.REQUIRES_NEW) public void bulkDelete(String entityName) { Query q = entityManager.createQuery( “DELETE FROM “+entityName); q.executeUpdate(); }
EJB-QL: nuove caratteristiche In precedenza abbiamo osservato come il linguaggio per eseguire le query definito nella specifica JPA sia una estensione del linguaggio EJB QL. In particolare, introduce varie migliorie che di seguito accenniamo: • • • • •
Aggiornamenti e cancellazioni “massive”; Operazioni di JOIN; Parole chiave GROUP BY e HAVING; Projection; Subquery.
Operazioni massive Per aggiornamenti e cancellazioni “massive” indichiamo una singola operazione il cui risultato è l’eliminazione o aggiornamento di un numero, anche elevato, di entità (in inglese i termini per indicare queste caratteristiche sono bulk update e bulk delete). Quando si esegue una operazione di cancellazione o di aggiornamento “massivo” valgono le seguenti regole: • • •
L’operazione “massiva” si applica all’entità “sorgente” della bulk operation e a tutte le sottoclassi di questa; L’operazione non si propaga alle entità relazionate con l’entità “sorgente” della bulk operation; Il persistence context non è sincronizzato con il risultato della operazione.
L’ultima tra le regole sopra elencate merita una riflessione. Poiché il persistence context non si sincronizza con il risultato di una operazione “massiva” è necessario che tali operazioni avvengano o all’inizio di una transazione o che siano eseguite in una transazione separata. Lo stralcio di codice che segue (due metodi presenti in un EJB di tipo session stateless) mostra un semplice esempio di bulk update e di bulk delete:
In particolare, con riferimento alla Figura 2, il metodo bulkUpdate() imposta il valore della colonna “anno di produzione” presente nella tabella PRODOTTO_SINGLE al valore null; mentre il metodo bulkDelete() elimina tutti i record rappresentati da una entity class. Osserviamo che se si invocato i metodi bulkUpdate() e bulkDelete() passando come parametro di input il valore “Libro_Single”, ossia il nome della entity class Libro, sono aggiornati ed eliminati i record relativi ad un libro; viceversa, se si invocano i due metodi passando come parametro di input il valore “Prodotto_Single”, ossia il nome della entity class Prodotto (la root class della gerarchia), sono aggiornati ed eliminati tutti i record relativi ad un libro, ad un video di tipo DVD e ad un video di tipo VHS.
Operazioni di JOIN Analizziamo ora il supporto introdotto in JPA per la gestione delle operazioni di JOIN. Con JPA possiamo avere i seguenti tipi di JOIN: • • •
JOIN; LEFT JOIN; FETCH JOIN.
Per esempio, sempre con riferimento alla Figura 2, se volessimo trovare tutti i prodotti che hanno associato almeno un autore potremmo scrivere la JOIN: @NamedQuery(name=”prodottiByAutoreJOIN”, query=”SELECT o FROM Prodotto_Single o JOIN o.autori”)
Viceversa, per ottenere i prodotti ai quali è associato o meno un autore scriveremo la LEFT JOIN: @NamedQuery(name=”prodottiByAutoreLEFT_JOIN”, query=”SELECT o FROM Prodotto_Single o LEFT JOIN o.autori”)
n.5 - novembre/febbraio 2008
43
JAVA Journal
focus
Le FETCH JOIN consentono di caricare le entità correlate specificate in una query, indipendentemente dalla politica di “loading” specificata nella definizione della relazione. Ricordiamo che in JPA, nella definizione di una relazione, la politica di caricamento può assumere i valori: EAGER e LAZY (Per ulteriori informazioni si veda [1]). La modalità di caricamento EAGER è generalmente meno efficiente, in quanto porta a caricare in memoria l’intera mappa delle relazioni; viceversa, la modalità di caricamento LAZY indica che le entità correlate sono caricate solo quando è necessario (purché vi sia un persistence context attivo). Ad esempio (sempre con riferimento alla Figura 2), se tra le entità Prodotto e Autore la relazione molti a molti fosse caratterizzata da una modalità di caricamento LAZY e volessimo eseguire una query il cui risultato siano i prodotti con valorizzato l’elenco di autori ad essi relazionati potremmo scrivere: @NamedQuery(name=”prodottiByAutoreFETCH_JOIN”, query=”SELECT o FROM Prodotto_Single o JOIN FETCH o.autori”)
Parole chiave GROUP BY e HAVING Nella precedente specifica di EJB QL era assente il supporto per le parole chiave GROUP BY e HAVING di SQL. Nella attuale specifica JPA tale supporto è stato introdotto: pertanto è possibile scrivere query che specificano raggruppamenti e condizioni tramite queste keyword. Per esempio per ottenere i conteggi del numero di video, sia VHS che DVD, che sono a colori ed in bianco e nero possiamo scrivere: @NamedQuery(name=”contaVideo”, query=”SELECT o.colore, COUNT(o) FROM Video_Single o GROUP BY o.colore”)
Proiezioni Con il termine Projection la specifica indica la possibilità che nel risultato di una query siano presenti solo alcuni attributi di una entità o di un insieme di entità. Si tratta di una ottimizzazione nel caso ad un client non interessi lavorare con la totalità degli attributi di una entità, ma solo con un sottoinsieme. Per esempio, se avessimo necessità di un elenco con solo i titoli e l’anno di produzione dei prodotti possiamo scrivere:
Subquery Concludiamo questa panoramica sulle nuove caratteristiche introdotte nel linguaggio parlando delle subquery. Attraverso una subquery è possibile eseguire delle query come parte della condizione di WHERE. L’uso delle subquery deve essere ben valutato in quanto può portare ad un degrado delle prestazioni. Ad esempio, se volessimo recuperare tutti i prodotti che sono stati realizzati da almeno due autori è possibile scrivere: @NamedQuery(name=”findWithSubQuery”, query=”SELECT o FROM Prodotto_Single o WHERE (SELECT count(e) FROM o.autori e) > 0”)
Conclusioni Con questo articolo concludiamo la panoramica sulla specifica JPA. Non è stato possibile trattare tutte le caratteristiche della persistence API, tuttavia sono evidenti due peculiarità: da un lato la semplicità di JPA come prodotto ORM (Object Relational Mapping) e dall’altro l’estrema flessibilità dell’API con il mapping delle relazioni e della ereditarietà. Sarà il tempo a dirci se JPA diverrà la modalità standard e più diffusa, tra sviluppatori e architetti del software, per persistere gli oggetti in Java. Tuttavia, per quanto si è detto e per la diffusione di Hibernate e TopLink (genitori naturali di JPA), le premesse sono estremamente positive. Riferimenti [1] : JPA (prima parte). Java Journal N. 4, anno I. [2]: http://www.jboss.com/. [3]: http://www.mysql.com/ [4]: http://www.hibernate.org/ [5] : Enterprise JavaBeans, v 2.1. http://java.sun.com/ products/ejb Bibliografia: [MEJ]: Mastering Enterprise JavaBeans 3.0, edito dalla Wiley Publishing, Inc. Autori: Rima Patel Sriganesh, Gerald Brose, Micah Silverman. [BAD]: Beginning EJB 3 Application Development, edito dalla Apress. Autori: Raghu R. Kodali and Jonathan Wetherbee with Peter Zadrozny
@NamedQuery(name=”findWithProjection”, query=”SELECT o.titolo, o.annoProduzione FROM Prodotto_Single o”)
Il risultato della query è un vettore in cui ogni elemento è un array di Object. Nell’esempio riportato, ogni array di Object ospitato nel vettore contiene due elementi: il titolo di tipo String e l’anno di produzione di tipo Date.
44
n.5 - novembre/febbraio 2008
Note Biografiche Fabio Staro dottore in Fisica è Responsabile Tecnico per i progetti Java presso la Direzione Ricerca ed Innovazione di Engineering Ingegneria Informatica S.p.A.