La
tecnologia JDO è stata già in passato trattata
da Mokabyte [1], e messa in relazione con la persistenza di
componenti EJB gestita dal container. In questa puntata verrà
invece confrontata con gli altri prodotti di persistenza di
POJO visti nelle puntate precedenti [2], [3], [4], [5].
Architettura
di JDO
Come molte tecnologie SUN per Java, JDO è principalmente
un insieme di specifiche ed interfacce che devono essere implementate
concretamente dai fornitori di prodotti, al fine di realizzare
le funzionalità richieste. Questa è la prima
grande differenza rispetto ai prodotti visti nelle puntate
precedenti: JDO è una interfaccia, mentre i vari Hibernate,
Castor ed OJB sono prodotti dotati principalmente di interfacce
proprietarie.
Un'altra differenza architetturale importante di JDO è
la presenza del componente bytecode enhancer, che si occupa
di modificare il bytecode delle classi che devono essere persistite,
aggiungendo il codice necessario a persistere la classe.
In questa fase le implementazioni JDO tipicamente producono
il DDL della base dati. In alcuni dei prodotti visti prima,
la modifica del bytecode può avvenire, ma in fase di
runtime.
La terza differenza fondamentale è che tutte le classi
di dominio che utilizzano JDO devono implementare l'interfaccia
PersistenceCapable che è, similarmente a Serializabile,
semplicemente una interfaccia di marcatura che individua le
entità persistenti. Questa interfaccia può essere
implementata esplicitamente nel codice o come risultato di
una operazione di enhancement.
Gli ultimi due elementi qui descritti sono quelli che rendono
JDO la tecnologia più invasiva tra quelle affrontate
in questa serie di articoli.
Interfacce,
interfacce
Le specifiche JDO definiscono una manciata di interfacce che
sovraintendono all'accesso delle funzionalità. Queste
sono:
-
PersistenceManagerFactory. Questa factory restituisce oggetti
di tipo PersistenceManager, ed è il punto di ingresso
per il quale i provider di servizi JDO devono offrire implementazione.
In ambienti J2EE, questa factory è ottenuta tipicamente
tramite una operazione di lookup su registri JNDI.
- PersistenceManager.
E' l'interfaccia implementata dai gestori del ciclo di vita
degli oggetti persistenti, che permette di creare e rimuovere
oggetti, effettuare la manipolazione di opzioni relative
alle transazioni, cambiare le opzioni del pooling di oggetti
ed accedere alle query;
- PersistenceCapable.
E' l'interfaccia di marcatura che tutti gli oggetti che
devono essere resi persistenti devono implementare;
- Transaction.
Per ciascun PersistenceManager esiste un oggetto Transaction
che gestisce il contesto transazionale entro il quale si
sta eseguendo l'operazione di persistenza di un oggetto.
In ambienti J2EE questa interfaccia permette di accedere
alle opzioni transazionali, mentre è il container
a gestire la transazione. In ambienti non J2EE l'interfaccia
rappresenta un vero e proprio servizio transazionale, che
deve essere invece implementato dal fornitore del servizio
JDO;
- Query.
Consente di specificare query di selezione sulla base dati
degli oggetti persistiti. La buona notizia è che
JDO implementa Object Query Language, la specifica di ODMG
per le query descritta in un articolo precedente [5].
Il ciclo di vita
Le specifiche JDO definiscono 10 stati in cui l'oggetto coinvolto
in JDO può passare (sette obbligatori e tre opzionali),
in funzione delle diverse chiamate ed operazioni che è
possibile eseguire. Questi sono:
-
transient. Un oggetto è transiente quando
non è considerato a livello JDO; Un oggetto transiente
diventa persistente (Persistent-new) nel momento in cui
viene reso persistente dalla chiamata PersistenceManager.makePersistent()
oppure quando viene referenziato da un campo persistente
di un oggetto persistente, quando quest'ultimo viene committato
o reso persistente;
- persistent-new.
L'istanza è stata appena resa persistente; gli viene
associata una identità;
- persistent-dirty.
Una istanza persistente, ancora non committata, di cui sono
stati cambiati i valori degli attributi;
- hollow.
Una istanza di un oggetto persistito con JDO, ma i suoi
attributi non sono ancora stati caricati dalla base dati;
- persistent-clean.
Un oggetto persistente, le cui copie in memoria e sulla
base dati sono perfettamente allineate;
- persistent-deleted.
Un oggetto che rappresenta una specifica istanza persistente
e che è stato cancellato nella transazione corrente;
- persistent-new-deleted.
Un oggetto appena creato e cancellato dalla transazione
corrente;
- persistent-nontransactional.
Un oggetto che rappresente una specifica istanza persistente
i cui valori sono caricati ma non consistenti con lo stato
della transazione;
- transient-clean.
Oggetto transiente inserito in un contesto transazionale
i quali valori non sono stati cambiati;
- transient-dirty.
Oggetto transiente inserito in un contesto transazionale
i quali valori sono stati cambiati;
La presenza di diversi stati permette di capire "a bocce
ferme" qual'è la condizione effettiva di un oggetto
in merito alla persistenza.
Un
esempio concreto
Per utilizzare in pratica JDO è è stata utilizzata
l'implementazione open source JPOX (Java Persistent Objects),
disponibile all'indirizzo http://www.jpox.org (anche Apache
OJB supporta parzialmente JDO, ma a parte la complessa configurazione,
sembrava molto più difficile ottenere un progetto funzionante).
Il progetto di riferimento è il consueto Mokatrack,
e durante lo sviluppo si vedrà sia JDO che estensioni
proprietarie JPOX che, come vedremo, sono un elemento praticamente
imprescindibile nell'utilizzo di qualsiasi implementazione
JDO.
Come gli altri motori di persistenza, JDO si appoggia su metadati
che definiscono la conformazione delle classi da rendere persistibili,
anche se questi sono solitamente meno dettagliati rispetto
a quelli presenti negli altri strumenti.
Questi metadati sono formattati in XML e contenuti in file
con estensione jdo. Ad esempio, la classe User è così
definita:
<?xml
version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo SYSTEM "file:/javax/jdo/jdo.dtd">
<jdo>
<package name="com.mokabyte.tracking.model">
<class name="User" identity-type="application"
objectid-class="com.mokabyte.tracking.model.support.LongKey">
<extension vendor-name="jpox" key="table-name"
value="Users"/>
<field
name="id" primary-key="true">
<extension vendor-name="jpox" key="column-name"
value="id"/>
</field>
<field name="account">
<extension vendor-name="jpox" key="column-name"
value="account"/>
<extension vendor-name="jpox" key="length"
value="max 15" />
</field>
<field name="password">
<extension vendor-name="jpox" key="column-name"
value="password"/>
<extension vendor-name="jpox" key="length"
value="max 15" />
</field>
<field name="name">
<extension vendor-name="jpox" key="column-name"
value="name"/>
<extension vendor-name="jpox" key="length"
value="max 25" />
</field>
<field name="email">
<extension vendor-name="jpox" key="column-name"
value="email"/>
<extension vendor-name="jpox" key="length"
value="max 40" />
</field>
</class>
</package>
</jdo>
Si
notino alcuni elementi importanti:
-
il DOCTYPE può essere di sistema o pubblico, ma in
quest'ultimo caso deve essere disponibile un accesso ad
Internet mentre si esegue l'enhancement o il programma.
Con un DOCTYPE di sistema, utilizzando l'indirizzo indicato
si esegue il puntamento alla copia di DTD interna nel JAR
di JDO;
- la
struttura annidata prevede le sezioni package, class e field.
Ciascuno di questi elementi definisce l'attributo "name",
indispensabile, che specifica il nome dell'elemento.
- gli
elementi <extension> definiscono le estensioni proprietarie,
indicando i metadati che sono significativi solo per il
particolare fornitore individuato dal valore dell'attributo
"vendor-name";
L'entità User, come Issue e Project, utilizzano una
modalità di JDO detta "application" e specificata
nell'attributo identity-type. Questo controlla la logica con
cui vengono impostate le chiavi primarie; nel caso di questa
modalità, è l'applicazione stessa che ne pilota
il funzionamento. In alternativa, è possibile indicare
il valore "datastore". In questo caso è JDO
a decidere la struttura delle tabelle e delle chiavi.
Un elemento di differenza di JDO è la necessità
di specificare, nel caso di chiavi gestite dall'applicazione,
la classe Java che implementa la chiave, in modo similare
a quanto si può fare nella tecnologia EJB. Non è
possibile dunque utilizzare primitive, ed è per questo
motivo che è stata creata la classe LongKey, che non
è altro che un wrapper sul tipo di dato long. Purtroppo
non è stato possibile utilizzare java.lang.Long, in
quanto questa classe non dispone di un costruttore di default,
elemento indispensabile per le classi gestite da JDO.
Per mappare l'entità User alla tabella users nella
forma utilizzata nelle precedenti versioni di Mokatrack, è
stato necessario indicare alcuni metadati specifici di JPOX,
ed in particolare:
-
table-name. Per indicare il nome della tabella da utilizzare;
- column-name.
Per indicare il nome della colonna da utilizzare;
- length.
Per specificare la dimensione massima del campo (solo nel
caso di VARCHAR, gli altri tipi di dati hanno necessità
differenti).
Enhancement
Il file JDO così compilato sarà utilizzato nella
fase di enhancement ed in quella di esecuzione. Per questo
motivo il file dovrà essere presente sia nel progetto
che nei binari di distribuzione. Per eseguire l'enhancement
delle classi di Mokatrack è stata utilizzata la reference
implementation di JDO, ed è stato aggiunto questo comando
a livello di build.xml:
<java
fork="yes" failonerror="yes"
classname="com.sun.jdori.enhancer.Main" classpathref="project-classpath">
<arg line="-v -f -d classes classes/com/mokabyte/tracking/model/Project.jdo
classes/com/mokabyte/tracking/model/Project.class"/>
</java>
Nota:
le librerie utilizzate dal progetto (sia per la fase di generazione
che per quella di esecuzione) sono: jdo.jar, jdori-enhancer.jar,
jdori.jar, jpox-1.0.0-beta-3.jar, log4j-1.2.8.jar, mysql-connector-java-3.0.10-stable-bin.jar
Accedere
alle informazioni
Per fare si che la factory iniziale di JDO ci fornisca l'accesso
all'implementazione JPOX, è necessario specifica il
valore della proprietà javax.jdo.PersistenceManagerFactoryClass
come segue:
Properties
properties = new Properties();
properties.setProperty("javax.jdo.PersistenceManagerFactoryClass",
"org.jpox.PersistenceManagerFactoryImpl");
A
questo punto è possibile impostare i parametri di connessione
al database, che, come di consueto, è MySQL:
properties.put("javax.jdo.option.ConnectionDriverName",
"com.mysql.jdbc.Driver");
properties.put("javax.jdo.option.ConnectionURL",
"jdbc:mysql://localhost:3306/mokatrack");
properties.put("javax.jdo.option.ConnectionUserName","root");
properties.put("javax.jdo.option.ConnectionPassword","****");
Seguono
alcune proprietà specifiche di JPOX, come il controllo
sulla creazione automatica delle tabelle e la loro validazione
(si noti che, sebbene la maggior parte dello schema sia già
presente, JPOX richiede almeno una tabella interna per sapere
a quale classe corrisponde ciascuna tabella):
properties.setProperty("org.jpox.autoCreateTables","true");
properties.setProperty("org.jpox.validateTables","false");
properties.setProperty("org.jpox.validateConstraints","false");
A
questo punto è possibile ottenere un PersistenceManager:
PersistenceManagerFactory
pmfactory = JDOHelper.getPersistenceManagerFactory( properties
);
pm = pmfactory.getPersistenceManager();
Che
fornisce la transazione corrente, che deve essere avviata:
currentTransaction
= pm.currentTransaction();
currentTransaction.begin();
A
questo punto è possibile eseguire una query, in particolare
per ottenere l'elenco dei progetti presenti nella base dati:
Query
query = pm.newQuery(Project.class);
Collection results = (Collection)query.execute();
L'elenco
viene ritornato come di consueto sotto forma di Collection,
che dovrà essere scritta nella pagina JSP, come di
consueto. Al termine della scrittura, però, è
necessario chiudere sia la transazione che il manager, pena
errori di runtime da parte di JPOX:
currentTransaction.commit();
pm.close();
Ottenere
un singolo progetto
Le query JDO supportano una serie di funzionalità di
selezione ed ordinamento, alcune delle quali sono state utilizzate
per eseguire la query di una singola instanza di Project.
Anche JPOX è dotato di cache, e quindi questa operazione
dovrebbe essere veloce.
Tramite il metodo newQuery(), viene passata la condizione
da verificare, in questo caso è che l'ID dell'entità
sia equivalente al parametro p_id. Una volta ottenuto l'oggetto
Query, è necessario dichiarare il parametro, specificando
che si tratta di un long e poi passare, al metodo execute()
un oggetto di classe conforme (in questo caso java.util.Long):
Query
query = pm.newQuery(Project.class, "id == p_id");
query.declareParameters("long p_id");
Long
projectIdLong = new Long( projectId );
Project
result = (Project)
((Collection)query.execute(projectIdLong)).iterator().next();
Questa
logica è utilizzata nel medesimo modo anche per ottenere
un utente in fase di inserimento di una nuova segnalazione.
Conclusioni
JDO è più invasivo rispetto a strumenti che
usano la riflessione, ma ha il vantaggio di poter essere più
prestante; la fase di enhancement è un ulteriore passaggio
che separa la programmazione dal test, rallentando il processo
di sviluppo, ma è molto similare a quanto richiesto
da RMI. In definitiva è uno svantaggio che potrebbe
essere comunque assorbito. Considerando poi che JDO è
lo standard della piattaforma Java, è facile prevedere
che progetti come Hibernate, Castor (e già lo stesso
OJB) prima o poi si adatteranno, fornendo perlomeno anche
una interfaccia JDO al fianco delle loro proprietarie.
Un
lato negativo
JPox è ancora in versione beta (sono state provate
la versione 3 e 4) e pare che ancora soffra di problemi in
fase di aggiornamento degli oggetti persistenti. In particolare
la funzione di aggiunta di una nuova segnalazione non funziona;
segnalato il problema sul forum, gli sviluppatori non hanno
ancora fornito una soluzione, che forse arriverà con
la versione 1.0.
Bibliografia
[1] Federico Pavoncelli -"Java Data Object, uno standard
per la persistenza degli oggetti java", Mokabyte, 12/2002
[2] Massimiliano Bigatti - "Motori di persistenza in
Java: parte I, Hibernate", Mokabyte, marzo 2004
[3] Massimiliano Bigatti - "Motori di persistenza in
Java: parte II, Castor", Mokabyte, aprile 2004
[4] Massimiliano Bigatti - "Motori di persistenza in
Java: parte III, Apache OJB", Mokabyte, maggio 2004
[5] Massimiliano Bigatti - "Motori di persistenza in
Java: parte IV, OJB e ODMG", Mokabyte, giugno 2004
Massimiliano
Bigatti é autore dei libri "Da Visual Basic a
Java", "Da Visual Basic a C#" e "Java
Web Application". E' certificato, tra le altre, come
SUN Certified Enterprise Architect for Java Platform, Enterprise
Edition Technology. E' content editor del portale dedicato
ai Web Services http://javawebservices.it.
|