MokaByte 87 - Luglio/Agosto 2004 
Motori di persistenza in Java
V parte: JDO
di
Massimiliano
Bigatti

intro

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.


MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it