|
L'uomo è il computer più straordinario di tutti
|
|
|
|
|
Il motore CMP su JBoss
By
Giulio Rambelli
7 giugno 2005
|
 |
|
L'application server JBoss mette a disposizione dello sviluppatore una robusta implementazione di un 'motore' CMP. Un motore CMP si occupa della gestione della persistenza di dati relativi a componenti EJB di tipo Entity risparmiando a chi lo utilizza l'onere di implementare il codice di accesso alla base dati; l'acronimo CMP sta infatti per Container Managed Persistence, con questo si vuole indicare che l' incarico di rendere persistenti i cambiamenti effettuati sui valori dei campi relativi ad un EJB di tipo entity è demandato esclusivamente al container provvisto delle corrette istruzioni. Si vedrà che naturalmente il container non si occuperà solo di rendere persistenti su un db gli aggiornamenti effettuati ma anche di creare la struttura del database creando le tabelle, relazionandole fra di loro, aggiornando così anche gli entity, tramite foreign keys o tabelle di relazione, il container si preoccuperà quindi anche di caricare i dati dell'entity quando lo riterrà necessario (ejbLoad) e altri compiti per i quali in BMP(Bean Managed Persistence) il bean provider doveva fornire tutta l'implementazione del caso. L'implementazione di un entity bean in CMP differisce da quella usata in BMP per il fatto che la classe del bean in CMP viene dichiarata abstract; questo perchè al suo interno dovrà dichiarare astratti i metodi di accesso ai campi che hanno una diretta corrispondenza a colonne del database, i cosiddetti cmp-fields, non dovrà quindi darne un'implementazione: di questo se ne occuperà il container!
|
|
|
I file ejb-jar.xml e jbosscmp-jdbc.xml
|
top
|
|
La dichiarazione di un entity CMP nel file ejb-jar.xml avrà naturalmente come valore per l'elemento <persistence-type> Container; quindi avrà un elemento <prim-key-class> per indicare la classe che identifica la chiave primaria o se la chiave primaria è di tipo primitivo, presenterà la corrispondente classe wrapper; un elemento <cmp-version> autoesplicativo, nuovo rispetto alla precedente versione CMP, che potrà essere o 1.x o, come di default, 2.x. Questo elemento è stato aggiunto per permettere di avere entity 1.x e 2.x insieme nella stessa applicazione. Un altro elemento nuovo è <abstract-schema-name> che permette di identificare il tipo di entity nelle query EJB-QL che vedremo più avanti. La configurazione CMP per JBOSS viene indicata nel file jbosscmp-jdbc.xml che si troverà insieme al file ejb-jar.xml nella directory META-INF. In questo file xml l'unico elemento obligatorio è <ejb-name> situato all'interno dell'elemento <entity> a sua volta all'interno di <enterprise-beans> che si trova all'interno dell'elemento 'root' <jbosscmp-jdbc>. L'elemento <ejb-name> è usato per riscontrare la configurazione di un entity dichiarato nel file ejb-jar.xml. Tutti gli altri elementi di questo file non sono dunque obbligatori ed hanno dei valori di default come l'elemento opzionale <table-name> che di default ha come valore il nome stesso dell'entity a cui si riferisce. Altri elementi di jbosscmp-jdbc.xml riguardano ad esempio il nome jndi per il datasource, altri indicano a JbossCMP se le tabelle vanno create al deploy dell'applicazione e/o rimosse all'undeploy, altri elementi verranno discussi in seguito. Anche se i cmp-fields non hanno subito cambiamenti nella versione CMP 2.0 per rispetto della funzionalità, essi non sono più dichiarati usando campi nella classe di implementazione del bean. Nella versione CMP 2.0, i cmp-fields non sono accessibili direttamente; adesso ogni campo cmp è dichiarato nella classe dell'entity bean attraverso un set di abstract accessor methods: i classici metodi get/set. I campi vengono quindi indirettamente dichiarati all'interno della classe del bean tramite i soli accessors methods che saranno rigorosamente public abstract (non avranno infatti un'implementazione), ma i cmp-fields vengono invece direttamente dichiarati nell'ejb-jar.xml e mappati alle rispettive colonne della tabella nel file jbosscmp-jdbc.xml in cui è anche possibile indicare il tipo di dato per la colonna e, se presente l'elemento <not-null>, JbossCMP aggiungerà NOT NULL alla fine della dichiarazione della colonna al momento di creare automaticamente la tabella per questo entity. Il fatto di avere metodi di accesso astratti ai cmp-fields da la possibilità di avere campi 'read only': nella versione precedente di CMP un bean provider poteva sempre cambiare il valore di un campo di un entity marcato come read-only, nella versione attuale invece, dato che è il container che si occupa di fornire l'implementazione dei metodi di accesso, questo non è più possibile. Questa caratteristica è ora estesa a livello dei campi cmp con l'aggiunta di due nuovi elementi, <read-only> e <read-time-out>, all'elemento <cmp-field>: se un campo è marcato a read only esso non verrà usato nelle INSERT o nelle UPDATE. Se una chiave primaria è read only, il metodo create lancerà una CreateException, se un metodo di accesso di tipo set viene invocato per un campo read only verrà lanciata una EJBException.
|
|
DVC - Dependent Value Class
|
top
|
|
E' un termine che identifica un campo cmp che è del tipo di una classe java diversa dai tipi automaticamente riconosciuti. Di default - ovvero non fornendo alcuna ulteriore istruzione - una DVC viene serializzata e memorizzata in una singola colonna del database; ci sono diverse tesi riguardo la memorizzazione persistente di classi in forma serializzata, comunque JbossCMP supporta la memorizzazione dei dati interni di una DVC - gli attributi della classe e i loro valori - in una o più colonne. Con una DVC, più colonne di una tabella possono essere mappate ad un JavaBean che deve seguire le "JavaBean Naming Specification", deve quindi avere metodi get e set per ogni attributo, deve implementare Serializable e avere un costruttore che non prende argomenti, gli attributi possono essere di qualsiasi tipo, anche un'altra DVC mappata o meno che sia, ma non può essere un EJB. Ad esempio un entity BMP può comunemente avere come attributo un JavaBean dal nome Location con tre attributi, lo stesso entity in 'versione' CMP presenterà nella classe del bean i metodi astratti getter e setter per l'attributo: public abstract Location getLocation(); public abstract void setLocation(Location location); mappato nel file ejb-jar.xml come un normale cmp-field: <cmp-field><field-name>location</field-name></cmp-field> presenterà nel file jbosscmp-jdbc.xml la mappatura per le colonne della tabella: <cmp-field> <field-name>location</field-name> <property> <property-name>country</property-name> <column-name>country</column-name> </property> <property> <property-name>address</property-name> <column-name>address</column-name> </property> <property> <property-name>phone</property-name> <column-name>phone</column-name> </property> </cmp-field> e nello stesso file sarà presente la dichiarazione della DVC in cui in particolare è da notare l'attributo <class> che indica dove si trova la classe del bean: <dependent-value-classes> <dependent-value-class> <description>A location</description> <class>com.mydomain.dvc.Location</class> <property> <property-name>country</property-name> <column-name>country</column-name> </property> <property> <property-name>address</property-name> <column-name>address</column-name> </property> <property> <property-name>phone</property-name> <column-name>phone</column-name> </property> </dependent-value-class> </dependent-value-classes>
|
|
CMR - Container Managed Relationships
|
top
|
|
Il container può gestire relazioni tra tabelle di tipo uno a uno, uno a molti e molti a molti mantenendo l'integrità referenziale, ma queste relazioni possono essere stabilite solo tra interfacce locali e questo esclude la possibilità di relazionare due entity che si trovanno in due diverse virtual machines. Per creare una relazione è necessario dichiarare i metodi di accesso astratti per un cmr-field come si fa per un cmp-field, ma in questo caso il metodo di accesso astratto getter per l'attributo avrà come valore di ritorno la remote interface o un java.util.Set (o java.util.Collection) di interfacce remote. Per creare una relazione tra due tabelle di tipo per esempio uno a molti come azienda - impiegato (un impiegato avrà una sola azienda, l'azienda molti impiegati), bisognerà dichiarare nella classe dell'entity 'Employee'(impiegato) i seguenti metodi: /* ritorna la remote interface dell'entity Company */ public abstract Company getCompany(); public abstract void setCompany(Company comp); e nella classe dell'entity 'Company':
/* ritorna un Set di interfacce remote dell'entity Employee */ public abstract Set getEmployees(); public abstract void setEmployees(Set emps); In una relazione molti a molti, invece, entrambi i set di metodi di accesso sono riferiti ad una java.util.Collection (o java.util.Set) di interfacce remote. Come si può vedere dal file ejb-jar.xml dell'applicazione di esempio, ogni relazione è dichiarata tramite un elemento <ejb-relation> all'interno dell'elemento <relationships>, e ogni elemento <ejb-relation> contiene due elementi <ejb-relationship-role> all'interno del quale, tra le altre cose, viene indicato il nome e il tipo del cmr-field, inoltre il tipo di relazione: uno a molti o molti a molti tramite l'elemento <multiplicity> e, nel caso di uno a molti, se è presente l'elemento vuoto <cascade-delete/>, si indica che se viene rimosso ad esempio il record relativo all'azienda, verranno rimossi anche tutti gli impiegati di quell'azienda. Nel file jbosscmp-jdbc.xml vengono mappate le relazioni dichiarate nell'ejb-jar.xml. Ci sono due modi per mappare le relazioni: tramite foreign key - tramite l'elemento vuoto <foreign-key-mapping/> - o tramite una tabella di relazione con gli elementi: <relation-table-mapping> <table-name>employee_task</table-name> </relation-table-mapping> Le relazioni uno a uno e uno a molti usano di default il metodo foreign key, mentre le relazioni molti a molti possono usare solo il mappaggio tramite 'relation table'; in questo file sono anche indicate, tramite l'elemento <key-fields>, la o le chiavi primarie dell'entity di cui viene dichiarata la relazione, all'interno di ciascun elemento <ejb-relationship-role>(presente anche in questo file come raffronto e mappaggio di ciò che viene dichiarato nell'ejb-jar.xml).
|
|
EJB-QL: metodi 'finder' e 'ejbSelect'
|
top
|
|
A questo punto resta da vedere come si può accedere ai dati sul db. Per quanto riguarda le comuni operazioni di inserimento e cancellazione come le 'create' e 'remove' e la modifica di singoli attributi di un entity, dal punto di vista dei metodi che il client deve chiamare, non cambia nulla rispetto al BMP, quello che cambia è che il 'bean provider'(lo sviluppatore) non dovrà fornire il codice per le singole operazioni di accesso al db, perché queste sono demandate interamente al 'motore' CMP. Quello che cambia col CMP è il modo con cui i dati vengono presi dal db, con la versione CMP 2.0, infatti, sono state introdotte le nuove 'features' EJB-QL e i metodi ejbSelect. Prima di questa versione ogni Application Server aveva un modo a se per specificare metodi di tipo 'finder', con l'introduzione dell'EJB-QL, invece, questo tipo di metodi viene specificato in un modo indipendente dalla piattaforma. I metodi per ottenere i dati dal db si possono così suddividere in: - 'finders': ritornano la remote o una collection di remote dello stesso tipo della Home interface in cui sono dichiarati.
- 'ejbSelects': possono ritornare entity di qualunque tipo o anche singoli attributi di un entity; sono dichiarati come metodi astratti nella classe di implementazione dell'entity.
Le specifiche EJB 2.0 richiedono che ogni metodo ejbSelect o finder (eccetto findByPrimaryKey) abbiano una query EJB-QL dichiarata in ejb-jar.xml, attualmente questa specifica non è rispettata in Jboss infatti il metodo findAll() presente nelle interfacce home degli entity dell'esempio funziona ugualmente ritornando una collection di remote perchè CMP si basa, per costruire la select necessaria, sul valore dell'elemento <abstract-schema-name> dichiarato in ejb-jar.xml. La query EJB-QL viene dichiarata all'interno dell'elemento <query> contenuto nell'elemento <entity>; di seguito mostro la dichiarazione del metodo 'findEmpsTasks' che ritorna una collection di remote dell'entity 'Task' relazionate, in una relazione molti a molti, con l'entity il cui nome(che è anche chiave primaria) viene passato al metodo come unico argomento(questo metodo serve solo come esempio di EJB-QL, dato che la collection che ritorna è la stessa che si ottiene chiamando il metodo getTasks() sulla remote dell'impiegato): <query> <query-method> <method-name>findEmpsTasks</method-name> <method-params> <method-param>java.lang.String</method-param> </method-params> </query-method> <ejb-ql><![CDATA[ SELECT OBJECT(g) FROM task g, employee h WHERE g MEMBER OF h.tasks AND h.name = ?1 ]]></ejb-ql> </query> La parte iniziale della query <![CDATA[...]]> è una notazione particolare di xml che serve ad evitare caratteri riservati come > o ' o altri, (come mettere il controslash (\) in java). I due caratteri '?1' indicano il punto in cui verrà usato l'unico argomento del metodo, come per un java.sql.PreparedStatement solo che quì il numero indica il numero dell'argomento, in questo caso il primo (e unico). L'EJB-QL presenta dei limiti nelle possibili query che ci si possono confezionare, questi limiti possono essere superati in parte o completamente sostituendo la query EJB-QL con una JBossQL, DynamicQL o DeclaredSQL ma queste sono estensioni non standard delle specifiche EJB 2.0 che quindi limitano la portabilità dell'applicazione. Nell'applicazione di esempio uso il metodo findAll() ogni volta che ho bisogno di reperire tutti i dati ad esempio degli impiegati e poi itero sulla collection ottenuta per prendere i dati dei singoli impiegati: Collection emp = employeeHome.findAll(); for(Iterator iter = emp.iterator();iter.hasNext();) { Employee empRemote = (Employee)iter.next(); myList.add(empRemote.getName()); myList.add(empRemote.getSurname()); myList.add(empRemote.getPhone()); } alla chiamata del metodo findAll() JBossCMP esegue una query per reperire tutte le primary key degli impiegati presenti sul db, poi per ogni impiegato trovato il motore CMP esegue un'altra query per prendere i dati di ognuno (nome, cognome,ecc.). Questo porta a due problemi: il primo è relativo all'elevato numero di query eseguite, il secondo riguarda il caricamento in ogni caso da parte di jBossCMP dei dati.Nell' articolo che seguirà introdurrò le ottimizzazioni necessarie per questo tipo di problemi e per altri e fornirò anche degli esempi e delle alternative a EJB-QL accennate sopra. Per una trattazione più approfondita di EJB-QL consiglio il sito www.ejb-ql.com.
|
|
L'applicazione di esempio
|
top
|
L'applicazione zippata scaricabile quì, gira su versioni di Jboss dalla 3.0.0 in poi, si appoggia al dbms Hypersonic presente nel bundle di jboss, crea le tabelle a deploy time, ed è settata per non rimuoverle con tutti i dati all'undeploy(si può cambiare settando a true l'elemento <remove-table> alla riga 10 del file jbosscmp-jdbc.xml dentro META-INF). Per far si che Hypersonic venga startato e che quindi tutto funzioni è necessario controllare che sia presente il file hsqldb-service.xml o hsqldb-ds.xml (a seconda della versione di jboss) in <jboss-home>/server/default/deploy e che il jndi name per il datasource in esso configurato sia DefaultDS, che se non è stato cambiato dovrebbe essere già tutto a posto per tutte le ultime versioni di jboss. Per compilare e deployare lanciare il build dentro CMP-Example/src/build (la directory CMP-Example si deve trovare allo stesso livello della directory di jboss) dopo avere inserito il nome della directory di jboss alla fine della riga 10 del file build.xml; richiamare l'applicazione dal browser tramite l'url: http://localhost:8080/CMP-Example. Struttura dell'applicazione (Figura 1):  Fig. 1 L'applicazione di esempio Come si vede dalla figura 1, l'applicazione è costituita da due jsp che si interfacciano ad una servlet che a sua volta si interfaccia ad un EJB di tipo session e da tre EJB di tipo Entity. Index.jsp (Figura 2) è la jsp che viene visualizzata al primo accesso all'applicazione. La jsp presenta un form tramite il quale è possibile inserire i dati che riguardano compagnia, impiegato e task (mansione) dell'impiegato. Se inseriti tutti insieme - company, employee e task - verranno create tra essi anche le relazioni - quell'impiegato avrà quella mansione e farà parte di quella compagnia - altrimenti si possono inserire da soli e, Index.jsp (Figura 2):  Fig. 2 L'applicazione di esempio tramite la jsp viewAll.jsp (Figura 3), accessibile tramite il link 'Show all records', sarà possibile creare, vedere ed eliminare le relazioni tra i dati. Per quanto riguarda le relazioni è da notare che la relazione company-employee è di tipo uno-a-molti - una compagnia può avere molti impiegati mentre un impiegato avrà una sola compagnia - e ciò, se è presente l'attributo vuoto <cascade-delete/> all'interno di <ejb-relationship-role> nel file ejb-jar.xml, comporta il fatto che eliminando il record relativo ad una compagnia, verranno eliminati anche i record relativi ai suoi impiegati, mentre l'eliminazione del record relativo ad un impiegato non comporterà altro. La relazione employee-task è invece di tipo molti-a-molti questo vuol dire che un impiegato potrà assumere più mansioni mentre una mansione potrà essere espletata da più impiegati. viewAll.jsp (Figura 3)  Fig. 3 L'applicazione di esempio L'applicazione implementa inoltre il pattern 'Session Facade' per evidenziare il fatto che, altrimenti, dato che gli EJB di tipo Entity CMP possono esporre unicamente interfacce locali, essi non sarebbero richiamabili da remoto.
|
|
Conclusioni
|
top
|
|
Come si è potuto vedere, a fronte del risultato di avere la persistenza dei dati gestita automaticamente c'è un relativamente grosso lavoro di configurazione iniziale che richiede tra l'altro una estrema cura di quello che si fa per ottenere quello che si vuole. Come in ogni situazione in cui sia richiesta la modifica di file xml 'a mano' bisogna anche qui fare molta attenzione perché una disattenzione porta ad errori molto difficili da individuare; d'altra parte in applicazioni molto grandi, l'adozione di questa tecnica permette modifiche molto veloci: si può notare che aggiungere, eliminare o modificare un campo di una tabella della base dati si riflette in brevi modifiche nei due file xml di configurazione e nella classe del bean interessato. Un grosso aiuto può venire dall'uso di strumenti quali XDoclet che permette, tramite l'aggiunta di speciali commenti nelle classi degli entity, di ottenere i file xml di configurazione opportunamente creati e adatti alle proprie esigenze.
|
|
|
| |
| JavaPortal è ideato da: |
 |

Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons
|
|
|