Академический Документы
Профессиональный Документы
Культура Документы
4 settembre-ottobre 2007
parte 1
Sun Microsystems ha eseguito una radicale rivisitazione della tecnologia di persistenza dei ` dati e la nuova specica, JPA, e una chiara semplicazione che si immette nel solco traccia` to dalle soluzioni per la persistenza piu diffuse, tra cui Hibernate, TopLink della Oracle e JDO.
Fabio Staro ` Dottore in Fisica e Responsabile Tecnico per i progetti Java presso la Direzione Ricerca ed Innovazione di Engineering Ingegneria Informatica S.p.A.
Limpaginazione 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 ne di ciascun articolo o vanno richieste direttamente agli autori. Il contenuto Infomedia e 2007 Infomedia e rila` sciato 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 identicazione della testata. Manoscritti e foto originali, anche se non pubblicati, non si restituiscono. Contenuto pubblicitario inferiore al 45%. La biograa dellautore riportata nellarticolo e sul sito www.infomedia.it e di norma quella disponibi` le nella stampa dellarticolo o aggiornata a cura dellautore stesso. Per aggiornarla scrivere a info@infomedia.it o farlo in autonomia allindirizzo http://mags.programmers.net/moduli/biograa
JAVA Journal
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-
cifica JPA per la persistenza e lobject 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 dellereditariet 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 Lapplicazione software che desideriamo realizzare volutamente semplice, al fine di approfondire lAPI e larchitettura 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.
20
JAVA Journal
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. 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 luso 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; }
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 dellapplicazione, ma rappresentano le classi del modello dati e sono in definitiva le entit che devono essere rese persistenti nel database. Le operazioni di business dellapplicazione sono modellate attraverso degli EJB 3.0 di tipo session stateless. Lesempio riportato stato verificato con lapplication server JBoss ver-
21
JAVA Journal
Osserviamo in dettaglio il codice riportato. Attraverso lannotazione @Entity, presente nel package javax.persistence informiamo il provider dei servizi JPA che la classe CasaEditrice una classe entity. Lattributo facoltativo name consente di specificare un nome attraverso il quale possibile referenziare la classe entity nelle query. Se lattributo name non specificato assume il valore di default, che il nome non qualificato della classe. Attraverso lannotazione @Table possiamo informare il provider dei servizi JPA su quale tabella presente nel database relazionale mappata dalla entity class (notiamo che possibile specificare oltre allattributo name, anche gli attributi schema e catalog per meglio identificare la tabella). Lannotazione @Column consente di mappare un attributo della classe su una colonna della tabella. Nel codice precedente lattributo 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 dellattributo 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 lannotazione @Transient. Precedentemente abbiamo evidenziato come una istanza di una entity class rappresenti un preciso record di una ta-
bella. Pertanto una entity class deve avere una primary key. Nel codice della classe CasaEditrice lattributo identificativo la chiave primaria dichiarata ed identificata attraverso lannotazione @Id. In casi complessi, la chiave primaria di una entity class pu essere un intero oggetto. JPA attraverso lannotazione @IdClass consente di dichiarare una classe come chiave primaria. Lultima annotazione nel codice precedente lannotazione @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 luso di una sequence nel database o di una colonna identit come strategia di generazione della chiave primaria, mentre il valo-
22
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, lEJB 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); } }
sincronizzazione che avviene al commit della transazione. In effetti possibile affermare che listanza della entity class nello stato managed (pi avanti dettaglieremo maggiormente il ciclo di vita di una entity class). Listanza rimane nello stato managed fino a quando non termina il persistence context o fino a quando listanza 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 allinterno 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 listanza dellEJB di tipo stateful allinterno 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. LEntity Manager distingue quattro diversi stati durante la vita di un entity: New; Managed; Detached; Removed.
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. Lannotazione @PersistenceContext consente di dichiarare il contesto di persistenza (si veda anche pi avanti dove parliamo del deployment dellapplicazione). 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, linterfaccia 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,
Quando viene creata, una istanza della classe entity si trova nello stato new, ossia listanza presente in memoria ma non associata ad un contesto di persistenza. Pertanto, le variazioni dei valori degli attributi dellistanza non sono sincronizzate con il database. Un entity nello stato new, dopo linvocazione sullEntity 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 dellistanza sono sincronizzate con il database quando la transazione sottoposta al commit o quando il processo di sincronizzazione viene richiesto direttamente attraverso il comando flush(). Unistanza nello stato managed transita nello stato detached quando termina il contesto di persistenza. Nello stato detached listanza 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 listanza ancora associata ad un contesto di persistenza, ma viene schedulata unoperazione di cancellazione sul database per il record
23
JAVA Journal
FIGURA 3 Ciclo di vita di un entity da questa rappresentato. Di seguito riportiamo i metodi principali presenti nellinterfaccia dellEntity Manager, direttamente inerenti al ciclo di vita di un entity:
public interface EntityManager { // Fa transitare unistanza 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> T merge(T entity); // Rimuove unistanza dal database public void remove(Object entity); // Controlla se un dato entity presente nel contesto di persistenza corrente public boolean contains(Object entity); }
solo record in relazione con un altro record. Nel modello dei dati dellesempio 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 dellesempio 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 dellesempio 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 dellesempio 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.
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 luso delle annotazioni. I tipi di relazione che possibile modellare tra le entity class sono: Uno a uno: la relazione indica che vi esattamente un
Inoltre, le relazioni possono essere unidirezionali o bidirezionali. Nel caso unidirezionale solo lentit 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 lanalizzare uno stralcio del codice sorgente della classe CasaEditrice dove presente la relazione uno a
24
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; public Indirizzo getIndirizzo() { return indirizzo; } public void setIndirizzo(Indirizzo indirizzo) { this.indirizzo = indirizzo; } }
lelemento cascade per default non vi alcuna propagazione delle operazioni di persistenza tra le entit correlate. Lelemento fetch nella annotazione @OneToOne pu assumere i valori presenti nella enumerazione javax.persistence.FetchTy pe che sono:
La relazione uno a uno che esiste tra una casa editrice e il suo indirizzo dichiarata attraverso lannotazione @OneToOne. Prima di esaminare nel dettaglio gli elementi cascade, fetch e targetEntity valorizzati nella dichiarazione della annotazione, analizziamo lannotazione @JoinColumn. Questa annotazione, consente di definire la colonna che funge da foreign key (ossia da chiave esterna). In particolare, attraverso lelemento name dellannotazione possibile specificare il nome della colonna che la foreign key, mentre con lelemento referencedColumnName si specifica il nome della colonna (presente nella tabella correlata) referenziata dalla foreign key. Lelemento cascade presente nellannotazione @OneToOne pu assumere i valori definiti nella enumerazione javax.persistence.CascadeType e sono: ALL; PERSIST; MERGE; REMOVE; REFRESH;
EAGER; LAZY;
Specificare
cascade={CascadeType.ALL}
Lelemento cascade indica il tipo di operazione che deve essere propagata sulla entit correlata. Ad esempio, se lentit 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() dellEntity Manager per lentit owner della relazione (nel nostro caso la classe CasaEditrice). Se non si specifica
In una relazione uno a uno, il valore di default dellelemento fetch EAGER, mentre sar LAZY nelle relazioni uno a molti e molti a molti. Il valore EAGER per lelemento 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 dellapplicazione. La modalit di caricamento EAGER generalmente meno efficiente, in quanto comporta il caricamento in memoria dellintera 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 dellindirizzo 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 allinterno di un persistence context attivo, come politica di caricamento normalmente pi efficiente il valore LAZY. Inoltre, possibile, attraverso luso 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 lannotazione @OneToOne anche caratterizzata dallelemento facoltativo targetEntity. Attraverso questo elemento possibile specificare la classe dellentit correlata, che nel codice riportato la classe Indirizzo. Il pi delle volte questo elemento pu essere omesso, poich la classe dellentit correlata viene ricavata dal provider JPA
25
JAVA Journal
mappedBy. Attraverso questo elemento indichiamo la propriet owner della relazione, che nellesempio lattributo casaEditrice presente nella classe della entit correlata, la classe Libro. Nella dichiarazione della annotazione @OneToMany omesso lelemento targetType, in quanto, grazie alluso dei generics, possibile dichiarare esplicitamente il tipo dellinsieme di oggetti correlati ad una casa editrice (Collection<Libro>). 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<Autore> autori = null; public Set<Autore> getAutori() { return autori; } public void addAutore(Autore autore) { if (null==autori) autori=new HashSet<Autore>(); if (!autori.contains(autore)) { autori.add(autore); autore.addLibro(this); } } }
dallanalisi del tipo dellattributo di relazione (nel precedente stralcio di codice, lelemento targetEntity pu essere in effetti omesso). Tuttavia, lelemento targetEntity necessario se, ad esempio, come tipo dellattributo di relazione non si specifica la classe dellentit correlata ma, ad esempio, linterfaccia 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; public CasaEditrice getCasaEditrice() { return casaEditrice; } public void setCasaEditrice(CasaEditrice casaEditrice) { this.casaEditrice = casaEditrice; } }
Osserviamo il codice della classe Libro. La relazione molti a uno con la classe CasaEditrice esplicitata attraverso luso dellannotazione @ManyToOne. Poich la relazione tra le due entit bidirezionale, osservando il codice della classe CasaEditrice notiamo, come si pu intuire, la presenza dellannotazione @OneToMany. In questa classe, oltre agli elementi cascade e fetch, dichiarato lelemento
26
JAVA Journal
Il supporto per la
gestione delle relazioni presente fin dalla specifica 2.0 degli EJB di tipo Entity
<description>: un testo opzionale <provider>: nome qualificato della classe del provider, nel nostro caso Hibernate, che implementa la specifica JPA; <transaction-type>: attributo del tag <persistence-unit>. JTA il valore di default altrimenti pu assumere il valore RESOURCE_LOCAL; <jta-data-source>: nome JNDI del datasource; <non-jta-data-source>: nome JNDI del datasource; <mapping-file>: file XML, in alternativa alle annotazioni, dove specificare le regole di mapping per le entity class; <jar-file>: archivio dove cercare le entity class disponibili per la persistence unit; <class>: le classi disponibili per la persistence unit; <exclude-unlisted-classes>: 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; <properties>: propriet specifiche del provider JPA.
Conclusioni In questo primo articolo abbiamo trattato i fondamenti della specifica JPA. Nel prossimo articolo parleremo della gestione dellereditariet e delle query statiche e dinamiche.
La relazione molti a molti esplicitata attraverso luso dellannotazione @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.
Bibliografia: Il deployment dellapplicazione 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 dellesempio della gestione della biblioteca:
<persistence> <persistence-unit name=GestioneBiblioteca> <jta-data-source>java:/theDataSource</jtadata-source> <provider>org.hibernate.ejb.HibernatePersistence </provider> </persistence-unit> </persistence>
[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.
27