Dopo aver introdotto nel precedente articolo gli argomenti relativi alla gestione della persistenza, questo mese approfondiamo il tema introducendo uno degli elementi più innovativi della nuova architettura, l‘Entity Manager.
Introduzione
Nelle scorse puntate si sono visti alcuni degli aspetti più importanti della nuova architettura EJB, in particolare si è visto il nuovo approccio basato sull‘utilizzo di annotazioni e di POJO e la possibilità di iniettare risorse all‘interno di una applicazione per mezzo del pattern IOC.
Questo mese proseguiamo la trattazione vedendo un po‘ più in profondità cosa c‘è di nuovo in EJB 3.0 per quello che concerne la gestione della persistenza. In particolare vedremo come l‘introduzione di un oggetto Entity Manager abbia permesso di realizzare un nuovo modo di gestire la sincronizzazione dei dati da e verso il sistema si persistenza.
Per capire come funzioni questo nuovo oggetto e quali riflessi abbia sul modo di lavorare, partiamo prima da un argomento collaterale che però funge da congiunzione con il passato: l‘implementazione dei metodi di ricerca per mezzo di EJBQL in EJB 3.0.
Metodi di ricerca ed EJBQL
La gestione di un entity bean prevede oltre alla creazione e cancellazione (argomenti visti in precedenza) anche la possibilità di ricercare un dato (quindi un entity) nel sistema. Seguendo la strada segnata dalla release precedente, anche in EJB 3.0 l‘implementazione dei metodi di ricerca avviene tramite metodi appositi il cui comportamento viene definito tramite un linguaggio XML detto EJBQL.
Tale linguaggio è relativamente semplice dato che non obbliga a dover codificare complesse istruzioni di interrogazioni (si pensi alle operazioni di Join o alle selezioni a raggruppamento e altro ancora), essendo queste demandate alla semantica della programmazione OOP insita in ogni componente EJB.
In questo articolo limiteremo l‘attenzione a come le cose sono cambiate relativamente allo sviluppo di codice EJB 3.0 rispetto alla versione precedente. Per maggiori approdondimenti su come funzioni EJBQL (e in particolare come implementare una Left o Inner Join con il concetto di navigazione delle relazioni) si può fare riferimento alla documentazione ufficiale oltre che al tutorial riportato presso [EJBQL].
Essendo la nuova specifica basata sul costrutto delle annotazioni, che hanno preso il posto dell‘XML, anche per la definizione dell‘EJBQL si deve utilizzare tale meccanismo; ad esempio volendo implementare un metodo di ricerca su tutti gli utenti del sistema (proseguendo con l‘esempio presentato nell‘articolo precedente) si potrebbe pensare di creare il seguente script SQL
SELECT * FROM USER;
che in EJBQL diventa
select o from User o
Tale script deve essere associato al bean per mezzo della annotazione @NamedQuery; quindi il bean diventa:
@Entity
@Table(name = "user")
@NamedQuery(name = "User.findAll", query = "select o from User o")
public class User implements Serializable {
...
}
Importante in questo breve passaggio, oltre alla definizione della query, il nome della query, che permette di identificare esattamente una istruzione di interrogazione nello scope della applicazione o del context, dove tutte le query sono di fatto memorizzate (si veda in fondo all‘articolo per maggiori approfondimenti).
Si immagini a questo punto quindi che un client esterno al container voglia ricavare l‘elenco degli utenti del sistema; seguendo il consueto schema architetturale (basato sul pattern Session Faà§ade), tale client non si deve connettere direttamente con il bean entity ma con un session bean di frontiera.
Tale session quindi esporrà sull‘interfaccia remota un metodo queryUserFindAll() la cui implementazione potrebbe essere la seguente:
public ListqueryUserFindAll() {
return em.createNamedQuery("User.findAll").getResultList();
}
Da notare che essendo EJB 3.0 facente parte di Java EE 5, e quindi di fatto Java 5, si può utilizzare il costrutto dei generics per definire nella firma del metodo il tipo lista dei valori ritornati.
Analizzando l‘interno del metodo si può osservare che il session esegue la chiamata della named query tramite l‘intermediazione dell‘oggetto em che è una istanza dell‘Entity Manager.
Tale oggetto viene iniettato (secondo il meccanismo della Dependency Injection come visto negli articoli precedenti) dal context direttament nel bean con la definizione della variabile em per mezzo della annotazione @PersistenceContext.
@PersistenceContext(unitName="users")
private EntityManager em;
Si noti l‘associazione del PersistenceContext allo unitName “users” che verrà debitamente parametrizzato tramite file di configurazione.
Questo non è l‘unico modo di gestire le operazioni di ricerca in un entity, dato che tramite l‘entity manager si possono eseguire operazioni dirette sul sistema di persistenza come mostrato nel proseguo dell‘articolo.
La filosofia di base è semplice: gli entity bean 3.0 limitano il loro raggio di intervento alla definizione della propria struttura (attributi, relazioni con altri bean, regole di mapping), ma non hanno nessuna competenza relativamente alle operazioni di sincronizzazione verso il persistence storage (contrariamente a quanto avveniva in EJB 2.x dove erano presenti lunghi e complessi metodi legati alla gestione del ciclo di vita del bean stesso).
Anche se ormai dovrebbe essere ben chiaro, un entity è un POJO che vive la sua vita nel container in maniera trasparente: la persistenza, ad esempio, viene gestita dall‘EntityManager sulla base delle regole definite nel bean.
Ecco la definizione completa del session bean UserManagerSessionBean con tutti i metodi di ricerca e di persistenza dell‘entity bean User:
public class UserManagerSessionBean implements UserManagerSessionRemote,
UserManagerSessionLocal {
@PersistenceContext(unitName="users")
private EntityManager em;
public UserManagerSessionBean() {
}
public Object mergeEntity(Object entity) {
return em.merge(entity);
}
public Object persistEntity(Object entity) {
em.persist(entity);
return entity;
}
public List queryAddressFindAll() {
return em.createNamedQuery("Address.findAll").getResultList();
}
public void removeAddress(Address address) {
address = em.find(Address.class, address.getId());
em.remove(address);
}
public ListqueryUserFindAll() {
return em.createNamedQuery("User.findAll").getResultList();
}
public void removeUser(User user) {
user = em.find(User.class, user.getId());
em.remove(user);
}
}
L‘Entity Manager
EJB 3.0 introduce l‘entity manager, un nuovo componente del framework dedicato alla gestione fisica della connessione con il database da un lato e alla persistenza degli entity dall‘altro.
Questo nuovo attore si è visto in azione nella parte precedente dell‘articolo, dove era il session bean che tramite la variabile em eseguiva le operazioni di ricerca e persistenza partendo dalle istruzioni EJBQL definite nell‘entity bean.
In realtà quanto appena visto è solo una parte delle nuove possibilità che sono possibili grazie alla intermediazione dell‘Entity Manager, il quale a conti fatti ha portato una piccola rivoluzione nella architettura di un application server: la specifica 3.0 infatti per quanto concerne il motore di persistenza permette di delegare a un framework di ORM (Object Relation Mapping) esterno al container le operazioni di sincronizzazione dei dati fra entities e database.
Il fatto di utilizzare un ORM esterno (che nella maggior parte dei casi è Hibernate) ha introdotto la possibilità di lavorare con un livello di dettaglio maggiore rispetto al passato.
Se infatti da un lato si può agire con una astrazione maggiore rispetto a prima (si pensi alle annotazioni che hanno preso il posto dell‘XML), sono possibili al contempo operazioni più a basso livello tipiche di strumenti come Hibernate.
Ad esempio è possibile adesso creare ed eseguire interrogazioni direttamente da dentro un metodo di un entity bean:
public ListfindAllUsers(){
Query q = manager.createQuery("select o from User o")");
}
Oppure, sempre agendo sull‘oggetto EntityManager è possibile eseguire in modo diretto una operazione di salvataggio per mezzo del metodo persist(). Per quanto concerne invece la ricerca, il metodo find() consente di trovare un entity in maniera diretta utilizzando l‘entity bean class name, filtrando la ricerca tramite la chiave primaria.
Per esempio:
String userId="la_chiave_primaria_del_bean_da_cercare";
User user = em.find(User.class, userId);
Appare evidente come questo nuovo modo di implementare la gestione della persistenza, oltre a offrire maggior potenza espressiva, tenda la mano a coloro che hanno lavorato a lungo con strumenti come Hibernate.
Può essere considerato un approccio alternativo (anche se non incompatibile) con quello basato su EJBQL al quale sono abituati tutti i programmatori EJB “puri”.
La configurazione dell‘Entity Manager
Un Entity Manager deve essere specificato e configurato per mezzo di XML, inserito nel file persistence.xml, contenuto nella directory META-INF. Tale file permette di specificare il nome del database da associare a un particolare schema di persistenza e consente di specificare il comportamento dell‘Entity Manager per quanto concerne le regole di traduzione e di mapping dei tipi (dialetto SQL, motore di persistenza da utilizzare etc.).
Ogni configurazione contenuta dentro tale file prende il nome di persistence unit; è possibile inserire più persistence unit associate a database differenti, o facenti uso di framework ORM diversi (Hibernate, Toplink etc.). Normalmente i vari parametri di configurazione contenuti in tale file sono specifici dell‘application server scelto o del database utilizzato per le operazioni di persistenza.
Di seguito è riportato un file di esempio che definisce una persistence unit collegata alla datasource con nome UserDS che sarà poi definita esternamente alla applicazione:
xsi:schemaLocation
="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0" >
org.hibernate.ejb.HibernatePersistence
java:/UserDS
Si noti in tale file la definizione del data provider associato al motore Hibernate così come il dialetto di mapping relativo al database MySQL.
Come già detto in precedenza le definizioni delle varie persistence unit sono “iniettate” nel bean dove necessario, tramite il meccanismo della Dependency Injection:
@PersistenceContext(unitName=" users")
EntityManager em;
Relativamente a JBoss AS (non ho avuto ancora modo di effettuare test comparativi con altri application server) è possibile omettere il valore della unitName nel caso in cui nel file persistence.xml sia definito un solo schema di persistenza oppure se nella applicazione si fa uso di un classloader specifico a scope applicazione.
Per spiegare brevemente questo secondo caso si consideri la situazione in cui una web application sia deployata in un file .ear, ossia nella directory META-INF deve essere presente un file jbossapp.xml. Tale file permette di specificare un classloader specifico tramite un nome univoco; ad esempio
usermanager_app=ejb3
Se non si definisce un classloader per l‘applicazione i vari persistence contexts definiti nelle singole applicazioni deployate nel container verranno registrati con scope globale (ovvero visibili da tutte le applicazioni); conseguentemente il meccanismo di injection viene eseguito con un scope globale (quindi in base al nome una persistence unit verrà ricavata fra tutte quelle definite nelle applicazioni a scope globale).
Questo permette da un lato di utilizzare un persistence context definito al di fuori della applicazione ma richiede di utilizzare nomi univoci per identificare quello che realmente si vuole utilizzare.
Infine si consideri che in JBoss AS il framework di riferimento per la persistenza di EJB 3.0 è Hibernate: è possibile consultare la lista dei parametri di configurazione e i relativi valori di default nel file
/server/deploy/ejb3.deployer/META-INF/persistence.properties
Conclusioni
Come si è avuto modo di vedere con questo articolo, la nuova specifica EJB 3.0 introduce un nuovo modo di gestire la persistenza tramite il componente Entity Manager. Questo nuovo approccio, che ha il preciso scopo di offrire maggiore flessibilità e libertà al programmatore, prende spunto dal modello tanto in voga in Hibernate. La novità è senza dubbio positiva dato che, al fianco di una maggior astrazione del modello di dati, è possibile eseguire operazioni a un livello di dettaglio più fine e personalizzato.
Sun ha di fatto perso la battaglia sui motori di persistenza (di fatto CMP 2.0 non ha avuto lo stesso successo di Hibernate): è una ironica conferma, come già ricordato negli articoli precedenti, di quanto sia valido l‘adagio che dice: “se non puoi battere il tuo avversario, alleati con lui”.
Riferimenti
[EJBQL] Tutorial su EJBQL: “Enterprise JavaBeansQuery Language”
http://java.sun.com/j2ee/tutorial/1_3-fcs/doc/EJBQL.html
[TB] “TrailBlazer”, tutorial con applicazione di esempio su EJB 3.0 dal gruppo di lavoro di JBoss
http://trailblazer.demo.jboss.com/EJB3Trail/