JJ — n. 4 — settembre-ottobre 2007 JPA: Java Persistence API di Fabio Staro parte 1 Sun Microsystems ha eseguito una ...
JJ — n. 4 — settembre-ottobre 2007
JPA: Java Persistence API
parte 1
di Fabio Staro Sun Microsystems ha eseguito una radicale rivisitazione della tecnologia di persistenza dei ` una chiara semplificazione che si immette nel solco tracciadati e la nuova specifica, JPA, e to dalle soluzioni per la persistenza piu` diffuse, tra cui Hibernate, TopLink della Oracle e JDO.
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 © 2007 Infomedia and released as Creative Commons 2.5 BY-NC-ND. Turing Club content is © 2007 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` © 2007 Infomedia e rilasciato con Licenza Creative Commons 2.5 BY-NC-ND. Il contenuto Turing Club e` © 2007 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
speciale Object/Relational Mapping
JAVA Journal
JPA: Java Persistence API prima parte
Sun Microsystems ha eseguito una radicale rivisitazione della tecnologia di persistenza dei dati e la nuova specifica, JPA, è una chiara semplificazione che si immette nel solco tracciato dalle soluzioni per la persistenza più diffuse, tra cui Hibernate, TopLink della Oracle e JDO.
>> di Fabio Staro (
[email protected])
G
li Entity Bean 2.x sono stati la tecnologia “ufficiale” in ambito Java EE per la persistenza dei dati. Tuttavia la complessità presente nello sviluppo e nel deployment ha fatto preferire a questi alternative più semplici, flessibili e performanti. Nel precedente numero di Java Journal abbiamo introdotto la specifica degli EJB versione 3.0, e in particolare ci siamo soffermati sugli EJB di tipo session stateless. Abbiamo evidenziato come per i nuovi EJB la Sun abbia eseguito una reingegnerizzazione della tecnologia, apportando sì nuove caratteristiche, ma soprattutto introducendo una notevole semplificazione. In questo articolo ci soffermeremo sui nuovi entity bean, evidenziando la radicale differenza architetturale presente tra gli entity 3.0 e le precedenti versioni 2.x e 1.x. Se fino alla versione 2.1 gli entity bean sono a tutti gli effetti degli EJB legati necessariamente ad un container EJB, i nuovi entity, descritti nella spe-
JPA è una specifica,
e pertanto possono esistere diversi provider che la implementano
20
n.4 - settembre/ottobre 2007
cifica JPA per la persistenza e l’object relational mapping, sono classi Java alle quali può essere associato un contesto di persistenza sia in un ambiente managed, ossia gestito da un Application Server, sia in un ambiente non managed, ossia Java standard, senza alcun Application Server. Dalla specifica JPA leggiamo esplicitamente: “This document is the specification of the Java API for the management of persistence and object/relational mapping with Java EE and Java SE”. Questa differenza architetturale caratterizza i nuovi entità, oltre ad una notevole semplificazione nella programmazione e nel deployment e a nuove caratteristiche. In questo primo articolo su JPA definiamo una entity class, il concetto del contesto di persistenza e la gestione delle relazioni. Nel prossimo articolo approfondiremo la gestione dell’ereditarietà ed il supporto alle query statiche e dinamiche. Prima di iniziare, osserviamo che JPA è una specifica, e pertanto possono esistere diversi provider che la implementano. Tra questi i più noti sono Hibernate e TopLink.
Una semplice biblioteca L’applicazione software che desideriamo realizzare è volutamente semplice, al fine di approfondire l’API e l’architettura JPA, eliminando qualsiasi complessità applicativa legata ad un particolare dominio di interesse. La Figura 1 riporta il diagramma Entità-Relazioni (ER) di un semplice repository per i libri di una biblioteca.
JAVA Journal
Object/Relational Mapping speciale
FIGURA 1 Diagramma Entità Relazioni Osservando la figura, possiamo notare che le entità principali nel diagramma sono quattro: • • • •
la tabella CASA_EDITRICE, la tabella INDIRIZZO, la tabella LIBRO, la tabella AUTORE.
Le relazioni che intercorrono tra le entità sono le seguenti: •
una casa editrice ha un solo indirizzo (relazione 1 a 1) e può, a sua volta, pubblicare molti libri (relazione 1 a molti);
•
un libro a sua volta appartiene ad una e una sola casa editrice (relazione molti a uno), e può essere stato scritto da vari autori
; •
un autore, a sua volta, può scrivere diversi libri (relazione molti a molti).
Attraverso un processo di realizzazione del tipo bottom-up, ossia partendo dal disegno del database e derivando da questo il modello delle classi, è possibile modellare le entità di dominio ottenendo il diagramma delle classi riportato in Figura 2. Le classi mostrate nella Figura 2 non sono, ovviamente, le uniche classi dell’applicazione, ma rappresentano le classi del modello dati e sono in definitiva le entità che devono essere rese persistenti nel database. Le operazioni di business dell’applicazione sono modellate attraverso degli EJB 3.0 di tipo session stateless. L’esempio riportato è stato verificato con l’application server JBoss ver-
sione 5.0 [1], con il database MySQL versione 5.0.27 [2] e con il provider JPA Hibernate [3]. Definizione di un entity Un entity, nella accezione della specifica JPA, è un POJO (acronimo di Plain Old Java Object), ossia è una semplice classe Java (che non deve implementare interfacce particolari o estendere classi di un particolare framework) i cui attributi mappano le colonne di una tabella e, in particolare, una istanza della classe rappresenta un ben preciso record di una tabella (questo perché una classe entity deve dichiarare una primary key). Il mapping tra la classe entity e una tabella è semplificato attraverso l’uso delle annotazioni. Lo stralcio di codice che segue mostra la classe CasaEditrice opportunamente annotata: @Entity(name=”CasaEditriceEntity”) @Table(name=”CASA_EDITRICE”) public class CasaEditrice implements Serializable { … @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name=”id_ce”) private int identificativo; private String ragioneSociale; private String telefono; private String fax; private String email; … }
n.4 - settembre/ottobre 2007
21
JAVA Journal
speciale Object/Relational Mapping
FIGURA 2 Il diagramma delle classi
Osserviamo in dettaglio il codice riportato. Attraverso l’annotazione @Entity, presente nel package javax.persistence informiamo il provider dei servizi JPA che la classe CasaEditrice è una classe entity. L’attributo facoltativo name consente di specificare un nome attraverso il quale è possibile referenziare la classe entity nelle query. Se l’attributo name non è specificato assume il valore di default, che è il nome non qualificato della classe. Attraverso l’annotazione @Table possiamo informare il provider dei servizi JPA su quale tabella presente nel database relazionale è mappata dalla entity class (notiamo che è possibile specificare oltre all’attributo name, anche gli attributi schema e catalog per meglio “identificare” la tabella). L’annotazione @Column consente di mappare un attributo della classe su una colonna della tabella. Nel codice precedente l’attributo identificativo della classe CasaEditrice è mappato sulla colonna id_ce della tabella CASA_EDITRICE. È importante sottolineare che se non avessimo specificato la annotazione @Column, come è stato fatto ad esempio per gli attributi ragioneSociale, telefono, fax ed email, si ha la mappatura automatica dell’attributo di classe su una colonna omonima della tabella (un esempio di configuration by exception). In effetti, per indicare al provider dei servizi JPA che un attributo di una entity class non deve essere reso persistente questo deve essere annotato con l’annotazione @Transient. Precedentemente abbiamo evidenziato come una istanza di una entity class rappresenti un preciso record di una ta-
22
n.4 - settembre/ottobre 2007
bella. Pertanto una entity class deve avere una primary key. Nel codice della classe CasaEditrice l’attributo identificativo è la chiave primaria dichiarata ed è identificata attraverso l’annotazione @Id. In casi complessi, la chiave primaria di una entity class può essere un intero oggetto. JPA attraverso l’annotazione @IdClass consente di dichiarare una classe come chiave primaria. L’ultima annotazione nel codice precedente è l’annotazione @GeneratedValue. Attraverso questa annotazione è possibile definire la regola di generazione per una chiave primaria. Per esempio, avendo scritto: strategy=GenerationType.AUTO
il provider JPA assume che la regola di generazione del valore della chiave primaria dipenda dal database utilizzato. Ad esempio, con il database MySQL una strategia di generazione impostata al valore AUTO indica che la colonna sulla tabella è del tipo AUTO_INCREMENT. Altri possibili valori presenti nella enumerazione GenerationType sono: • • •
SEQUENCE, IDENTITY, TABLE.
I valori SEQUENCE e IDENTITY indicano l’uso di una sequence nel database o di una colonna identità come strategia di generazione della chiave primaria, mentre il valo-
JAVA Journal re TABLE indica che il provider JPA deve assegnare valori univoci usando una tabella del database.
Il Persistence Context La logica di business della applicazione di esempio è modellata attraverso EJB 3.0 di tipo session stateless. Per esempio, l’EJB GestoreCasaEditriceBean ha una serie di metodi per la gestione di una casa editrice. Di seguito riportiamo uno stralcio del codice: @Stateless (name=”CasaEditrice”) @TransactionManagement(TransactionManagementType. CONTAINER) public class GestoreCasaEditriceBean implements GestoreCasaEditrice { … @PersistenceContext(type=PersistenceContextType.TRANSACTION,unitName=”GestioneBiblioteca”) private EntityManager manager = null; public boolean inserisci(CasaEditrice ce) { try { manager.persist(ce); } catch(Exception e) { return false; } return true; } public CasaEditrice getCasaEditrice(int identificativo) { return manager.find(CasaEditrice.class, identificativo); } … }
Osservando il codice sopra riportato osserviamo che le operazioni di persist() (di fatto una operazione di insert sul database) e di find() (operazione di ricerca) sono invocate su un oggetto EntityManager iniettato nel bean GestoreCasaEditriceBean dal container EJB. L’annotazione @PersistenceContext consente di dichiarare il contesto di persistenza (si veda anche più avanti dove parliamo del deployment dell’applicazione). Il contesto di persistenza è la connessione tra le istanze delle entity class che risiedono in memoria e il database sottostante, ed è gestito attraverso una nuova API, l’interfaccia EntityManager sempre presente nel package javax.persistence. Attraverso il metodo persist() aggiungiamo una istanza di una entity class al persistence context e da quel momento in poi tutte le modifiche che avvengono sullo stato della entity class sono schedulate per la sincronizzazione sul database,
Object/Relational Mapping speciale
sincronizzazione che avviene al commit della transazione. In effetti è possibile affermare che l’istanza della entity class è nello stato managed (più avanti dettaglieremo maggiormente il ciclo di vita di una entity class). L’istanza rimane nello stato managed fino a quando non termina il persistence context o fino a quando l’istanza non è esplicitamente rimossa. Il persistence context associato ad un EntityManager può essere di due tipi (dichiarati nella enumerazione PersitenceContextType): • •
TRANSACTION; EXTENDED;
Un persistence context di tipo TRANSACTION termina quando si conclude la transazione all’interno della quale sono eseguite le operazioni. Da quel momento in poi tutte le istanze delle entity class non sono più nello stato managed, e pertanto non vi è sincronizzazione tra le modifiche che avvengono sulla istanza in memoria ed il database. Un persistence context di tipo EXTENDED termina quando viene rimossa dal container l’istanza dell’EJB di tipo stateful all’interno della quale è dichiarato. Le istanze delle entity class, in tale contesto, rimangono nello stato managed durante le invocazioni successive dei metodi di uno stateful session EJB.
Ciclo di vita di un entity Il diagramma degli stati riportato in Figura 3 sintetizza il ciclo di vita di un entity. L’Entity Manager distingue quattro diversi stati durante la vita di un entity: • • • •
New; Managed; Detached; Removed.
Quando viene creata, una istanza della classe entity si trova nello stato new, ossia l’istanza è presente in memoria ma non è associata ad un contesto di persistenza. Pertanto, le variazioni dei valori degli attributi dell’istanza non sono sincronizzate con il database. Un entity nello stato new, dopo l’invocazione sull’Entity Manager di una operazione di persist(), transita nello stato managed e diviene associata ad un contesto di persistenza, avendo una ben precisa identità di persistenza determinata dalla corrispondente chiave primaria. In questo stato, le variazioni dei valori degli attributi dell’istanza sono sincronizzate con il database quando la transazione è sottoposta al commit o quando il processo di sincronizzazione viene richiesto direttamente attraverso il comando flush(). Un’istanza nello stato managed transita nello stato detached quando termina il contesto di persistenza. Nello stato detached l’istanza ha ancora una identità di persistenza ma, non essendo più associata con un contesto di persistenza, non sincronizza variazioni dei suoi attributi con il database. Infine, una istanza della entity class, a fronte di una operazione di remove(), transita dallo stato managed allo stato removed. In questo stato l’istanza è ancora associata ad un contesto di persistenza, ma viene schedulata un’operazione di cancellazione sul database per il record
n.4 - settembre/ottobre 2007
23
JAVA Journal
speciale Object/Relational Mapping
FIGURA 3 Ciclo di vita di un entity da questa rappresentato. Di seguito riportiamo i metodi principali presenti nell’interfaccia dell’Entity Manager, direttamente inerenti al ciclo di vita di un entity: public interface EntityManager { … // Fa transitare un’istanza nello stato managed e persistente public void persist(Object entity); // Esegue il merge dello stato di un entity in un contesto di persistenza. public T merge(T entity); // Rimuove un’istanza dal database public void remove(Object entity); // Controlla se un dato entity è presente nel contesto di persistenza corrente public boolean contains(Object entity); … }
La gestione delle relazioni Il supporto per la gestione delle relazioni è presente fin dalla specifica 2.0 degli EJB di tipo Entity. Con gli EJB 3.0 la gestione delle relazioni è stata notevolmente semplificata attraverso l’uso delle annotazioni. I tipi di relazione che è possibile modellare tra le entity class sono: •
24
Uno a uno: la relazione indica che vi è esattamente un
n.4 - settembre/ottobre 2007
solo record in relazione con un altro record. Nel modello dei dati dell’esempio è la relazione che esiste tra una casa editrice e il suo indirizzo; •
Uno a molti: la relazione indica che un particolare record è in relazione con molti altri record. Nel modello dei dati dell’esempio è la relazione che intercorre tra una casa editrice e i libri da questa pubblicati. Infatti, una casa editrice pubblica molti libri;
•
Molti a uno: la relazione indica che molti record sono in relazione con un solo particolare record. Nel modello dei dati dell’esempio è la relazione tra i libri e la casa editrice che li pubblica;
•
Molti a molti: la relazione indica che molti record sono in relazione con molti altri record. Nel modello dei dati dell’esempio è la relazione che esiste tra un libro e i suoi autori. Infatti, un libro può essere scritto da più autori, e un autore, viceversa, può scrivere vari libri.
Inoltre, le relazioni possono essere unidirezionali o bidirezionali. Nel caso unidirezionale solo l’entità proprietaria della relazione è a conoscenza della relazione, mentre in una relazione bidirezionale entrambe le entità ne sono a conoscenza. Ad esempio, nel diagramma delle classi riportato in Figura 2 la relazione che esiste tra una casa editrice e il suo indirizzo è stata modellata come unidirezionale, e pertanto, data una casa editrice è possibile ricavare il suo indirizzo e non il viceversa; mentre la relazione tra una casa editrice e i libri da questa pubblicati è modellata come bidirezionale in quanto, data una casa editrice, è possibile ricavare i libri da questa pubblicati, e dato un libro, è possibile conoscere la casa editrice che lo ha pubblicato. Iniziamo con l’analizzare uno stralcio del codice sorgente della classe CasaEditrice dove è presente la relazione uno a
JAVA Journal uno unidirezionale con la classe Indirizzo: @Entity(name=”CasaEditriceEntity”) @Table(name=”CASA_EDITRICE”) public class CasaEditrice implements Serializable { … @OneToOne(cascade=CascadeType.ALL,fetch=FetchT ype.EAGER,targetEntity=Indirizzo.class) @JoinColumn(name=”indirizzo”, referencedColumn Name=”id_ indirizzo”) private Indirizzo indirizzo = null;
Object/Relational Mapping speciale
l’elemento cascade per default non vi è alcuna propagazione delle operazioni di persistenza tra le entità correlate. L’elemento fetch nella annotazione @OneToOne può assumere i valori presenti nella enumerazione javax.persistence.FetchTy pe che sono:
Una entity class deve
public Indirizzo getIndirizzo() { return indirizzo; }
avere una primary key
public void setIndirizzo(Indirizzo indirizzo) { this.indirizzo = indirizzo; } … }
La relazione uno a uno che esiste tra una casa editrice e il suo indirizzo è dichiarata attraverso l’annotazione @OneToOne. Prima di esaminare nel dettaglio gli elementi cascade, fetch e targetEntity valorizzati nella dichiarazione della annotazione, analizziamo l’annotazione @JoinColumn. Questa annotazione, consente di definire la colonna che funge da foreign key (ossia da chiave esterna). In particolare, attraverso l’elemento name dell’annotazione è possibile specificare il nome della colonna che è la foreign key, mentre con l’elemento referencedColumnName si specifica il nome della colonna (presente nella tabella correlata) referenziata dalla foreign key. L’elemento cascade presente nell’annotazione @OneToOne può assumere i valori definiti nella enumerazione javax.persistence.CascadeType e sono: • • • • •
ALL; PERSIST; MERGE; REMOVE; REFRESH;
Specificare cascade={CascadeType.ALL}
è equivalente ad aver specificato cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH}
L’elemento cascade indica il tipo di operazione che deve essere “propagata” sulla entità correlata. Ad esempio, se l’entità correlata (che nel nostro esempio è la classe Indirizzo) assume il valore PERSIST, questa viene resa persistente sul database nel momento in cui si invoca il metodo persist() dell’Entity Manager per l’entità owner della relazione (nel nostro caso la classe CasaEditrice). Se non si specifica
• •
EAGER; LAZY;
In una relazione uno a uno, il valore di default dell’elemento fetch è EAGER, mentre sarà LAZY nelle relazioni uno a molti e molti a molti. Il valore EAGER per l’elemento fetch indica che le entità correlate vengono caricate assieme (una sorta di caricamento anticipato); viceversa, il valore LAZY indica che le entità correlate vengono caricate solo quando è necessario (una forma di caricamento differito). La scelta della modalità di caricamento delle entità ha un impatto rilevante sulle prestazioni dell’applicazione. La modalità di caricamento EAGER è generalmente meno efficiente, in quanto comporta il caricamento in memoria dell’intera mappa delle relazioni. Ad esempio, osserviamo il diagramma delle classi e il diagramma Entità/Relazioni del nostro esempio: se per tutte le entità correlate la politica di caricamento fosse impostata al valore EAGER, allora una semplice query di ricerca di una casa editrice comporterebbe il caricamento anche dell’indirizzo e, soprattutto, di tutti i libri da questa pubblicati; e per ogni libro si avrebbe il caricamento degli autori. Si intuisce facilmente che impostare tutte le relazioni al valore EAGER porta facilmente ad un degrado delle prestazioni! Se si sta operando su un insieme di entità all’interno di un persistence context attivo, come politica di caricamento è normalmente più efficiente il valore LAZY. Inoltre, è possibile, attraverso l’uso delle fetch join (argomento che approfondiremo nel prossimo articolo), personalizzare la modalità di caricamento delle entità con un maggiore livello di granularità (di fatto, caso per caso, attraverso query specifiche). Nello precedente stralcio di codice l’annotazione @OneToOne è anche caratterizzata dall’elemento facoltativo targetEntity. Attraverso questo elemento è possibile specificare la classe dell’entità correlata, che nel codice riportato è la classe Indirizzo. Il più delle volte questo elemento può essere omesso, poiché la classe dell’entità correlata viene ricavata dal provider JPA
n.4 - settembre/ottobre 2007
25
JAVA Journal
speciale Object/Relational Mapping
dall’analisi del tipo dell’attributo di relazione (nel precedente stralcio di codice, l’elemento targetEntity può essere in effetti omesso). Tuttavia, l’elemento targetEntity è necessario se, ad esempio, come tipo dell’attributo di relazione non si specifica la classe dell’entità correlata ma, ad esempio, l’interfaccia da questa implementata. La classe CasaEditrice ha una relazione bidirezionale uno a molti con la classe Libro. Di seguito riportiamo uno stralcio del codice sorgente della classe Libro: @Entity(name=”LibroEntity”) @Table(name=”LIBRO”) public class Libro implements Serializable { … @ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER) @JoinColumn(name=”casaEditrice”, referencedColumnName=”id_ce”) private CasaEditrice casaEditrice = null;
mappedBy. Attraverso questo elemento indichiamo la proprietà owner della relazione, che nell’esempio è l’attributo casaEditrice presente nella classe della entità correlata, la classe Libro. Nella dichiarazione della annotazione @OneToMany è omesso l’elemento targetType, in quanto, grazie all’uso dei generics, è possibile dichiarare esplicitamente il tipo dell’insieme di oggetti correlati ad una casa editrice (Collection). Per concludere la panoramica sui tipi di relazioni, analizziamo la relazione bidirezionale molti a molti tra le entità Libro e Autore. Di seguito è riportato uno stralcio del codice della classe Libro: @Entity(name=”LibroEntity”) @Table(name=”LIBRO”) public class Libro implements Serializable { … @ManyToMany(mappedBy=”libri”, cascade=CascadeType.ALL, fetch=FetchType.EAGER) private Set autori = null;
public CasaEditrice getCasaEditrice() { return casaEditrice; } public void setCasaEditrice(CasaEditrice casaEditrice) { this.casaEditrice = casaEditrice; } …
public Set getAutori() { return autori; } public void addAutore(Autore autore) { if (null==autori) autori=new HashSet(); if (!autori.contains(autore)) { autori.add(autore); autore.addLibro(this); } } …
}
e della classe CasaEditrice: @Entity(name=”CasaEditriceEntity”) @Table(name=”CASA_EDITRICE”) public class CasaEditrice implements Serializable { … @OneToMany(mappedBy=”casaEditrice”, cascade=CascadeType.ALL, fetch=FetchType.EAGER) private Collection libri = null; public Collection getLibri() { return libri; } public void addLibro(Libro libro) { if (this.getLibri() == null) this.libri = new ArrayList(); libro.setCasaEditrice(this); this.getLibri().add(libro); } … }
Osserviamo il codice della classe Libro. La relazione molti a uno con la classe CasaEditrice è esplicitata attraverso l’uso dell’annotazione @ManyToOne. Poiché la relazione tra le due entità è bidirezionale, osservando il codice della classe CasaEditrice notiamo, come si può intuire, la presenza dell’annotazione @OneToMany. In questa classe, oltre agli elementi cascade e fetch, è dichiarato l’elemento
26
n.4 - settembre/ottobre 2007
}
e della classe Autore: @Entity(name=”AutoreEntity”) @Table(name=”AUTORE”) public class Autore implements Serializable { … @ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) @JoinTable(name=”LIBRO_AUTORE”, joinColumns={@JoinColumn(name=”autore_ id”,referencedColumnName=”id_autore”)}, inverseJoinColumns={@JoinColumn (name=”libro_ id”, referencedColumnName=”id”)} private Set libri = null; public void addLibro(Libro libro) { if (null == libri) libri=new HashSet(); if (!libri.contains(libro)) {
JAVA Journal
Object/Relational Mapping speciale
Analizziamo i tag XML di una persistence unit:
Il supporto per la gestione delle relazioni è presente fin dalla specifica 2.0 degli EJB di tipo Entity
• • • • • • • • •
•
: un testo opzionale : nome qualificato della classe del provider, nel nostro caso Hibernate, che implementa la specifica JPA; : attributo del tag . JTA è il valore di default altrimenti può assumere il valore RESOURCE_LOCAL; : nome JNDI del datasource; : nome JNDI del datasource; : file XML, in alternativa alle annotazioni, dove specificare le regole di mapping per le entity class; : archivio dove cercare le entity class disponibili per la persistence unit; : le classi disponibili per la persistence unit; : se questo elemento è presente solo le entity class o gli archivi definiti con i tag mapping-file, jar-file e class sono disponibili nella persistence unit; : proprietà specifiche del provider JPA.
libri.add(libro); libro.addAutore(this); } } public Collection getLibri() { return libri; } …
Conclusioni In questo primo articolo abbiamo trattato i fondamenti della specifica JPA. Nel prossimo articolo parleremo della gestione dell’ereditarietà e delle query statiche e dinamiche.
}
Riferimenti
La relazione molti a molti è esplicitata attraverso l’uso dell’annotazione @ManyToMany. Osserviamo che nella classe Autore attraverso la annotazione @JoinTable è possibile specificare la tabella associativa che di fatto realizza la relazione nello schema Entità/Relazioni di Figura 1.
[1]: http://www.jboss.com/ [2]: http://www.mysql.com/ [3]: http://www.hibernate.org/
Bibliografia: Il deployment dell’applicazione Le entity class sono raccolte in persistence unit. Una persistence unit è una unità logica di entity class, con i metadati per il mapping e le configurazioni specifiche per il database ed il provider JPA. Una persistence unit è definita in un file descrittore chiamato persistence.xml che deve essere presente nella cartella META-INF di un archivio jar o di un archivio war o ear. Di seguito riportiamo il file persistence.xml dell’esempio della gestione della biblioteca: java:/theDataSource org.hibernate.ejb.HibernatePersistence
[4] R. P. Sriganesh, G. Brose, M. Silverman “Mastering Enterprise JavaBeans 3.0”, Wiley Publishing, Inc., 2006. [5] R. R. Kodali, J. Wetherbee e P. Zadrozny “Beginning EJB 3 Application Development”, Apress, 2006.
Note Biografiche Jacopo Giudici si occupa di applicazioni per il Web, di RFID e di domotica su piattaforme J2ME, J2EE e .NET. Progetta e tiene corsi di formazione aziendali sulla sicurezza delle reti informatiche e sulla programmazione Java per ambienti distribuiti.Ha lavorato per Microsoft, BottegaVerde, CONAD, Confartigianato e ha pubblicato una collana di 6 libri con DeAgostini Scuola sui Sistemi Elettronici-Informatici per il triennio degli istituti tecnici superiori.
n.4 - settembre/ottobre 2007
27