Nello
scorso numero [3] sono state messe in atto tutte le configurazioni
necessarie a rendere operativo il modello di mokatrack (lo
riportiamo in figura 1 per comodità) all'interno del
framework OJB.
Figura 1 - Entità del progetto d'esempio
In
questo numero utilizzeremo due diverse API per l'accesso a
queste informazioni, cominciando da PersistenceBroker per
poi passare alle API standard ODMG.
Elencare
i progetti
La classe ProjectList è il punto di inizio dell'applicazione,
sulla quale viene invocato il metodo getProjects(), che ritorna
l'elenco dei progetti presenti sulla base dati. Le API native
di OJB utilizzano l'oggetto PersistenceBroker, ottenibile
da una factory specifica. A Questo punto è sufficiente
indicare che si desiderano tutti gli oggetti, specificando
una query sulla classe Project e con Criteria vuoto e poi
chiamare il metodo getCollectionByQuery():
public
Collection getProjects() {
PersistenceBroker broker = null;
Collection
results = null;
try
{
broker
= PersistenceBrokerFactory.defaultPersistenceBroker();
QueryByCriteria
query = new QueryByCriteria(Project.class, new Criteria());
results
= broker.getCollectionByQuery(query);
}
finally {
if
(broker != null) {
broker.close();
}
}
return
results;
}
Il
metodo ritorna un oggetto di tipo Collection, che contiene
oggetti di tipo Project. Per ottenere un solo elemento la
procedura è similare, ma è necessario specificare
di quale istanza si tratta, ad esempio impostando nel criterio
di selezione la chiave (id) ed il relativo valore. Questo
è fattibile utilizzando il metodo addEqualTo() definito
nella classe Criteria. A questo punto, visto che il risultato
che si aspetta è un singolo oggetto, si chiamerà
il metodo getObjectByQuery() il cui risultato dovrà
essere convertito, tramite cast, al tipo Project:
Criteria
crit = new Criteria();
crit.addEqualTo("id", projectId);
QueryByCriteria
query = new QueryByCriteria(
Project.class,
crit
);
result = (Project)broker.getObjectByQuery(query);
Tabella 1 - Metodi della classe Criteria
La
classe Critera consente, oltre a specificare le classiche
condizioni utilizzate in SQL (alcuni metodi sono riassunti
in tabella1), consente di aggiungere i connettivi logici AND
ed OR per unire le singole condizioni ed anche di operare
direttamente con le colonne del database. I metodi visti finora,
infatti, operano con gli attributi delle classi, che vengono
poi tradotti in colonne del database da OJB. La classe Criteria
permette di specificare condizioni direttamente sulle colonne
delle tabelle, ed operare confronti tra questi e gli attributi
della classe. E' poi possibile specificare le condizioni di
ORDER BY, attraverso i metodi addOrderXXX().
OJB
dispone anche della possibilità di eseguire query sulla
base di un template, un oggetto del tipo richiesto ma con
caricati solo i dati relativi alle condizioni di WHERE da
utilizzare per la selezione. Ad esempio, per ottenere un progetto
attraverso la sua id si potrebbe scrivere:
Project
projectTemplate = new Project();
projectTemplate.setId( Long.parseLong( projectId ) );
QueryByCriteria
query = new QueryByCriteria( projectTemplate );
result = (Project)broker.getObjectByQuery(query);
Questo
approccio alternativo non funziona però con la nostra
implementazione della classe Project, in quanto, oltre alla
chiave id di tipo long, contiene la chiave userId, sempre
di tipo long che, non inizializzata, contiene il valore 0.
OJB dunque costruisce una query con condizione WHERE id=%projectId%
AND id_lead=0, logicamente errata e che non troverà
mai nessun record, visto che gli identificativi degli utenti
partono almeno da 1.
Inserimento
L'inserimento di una nuova segnalazione, implementato nel
metodo setAdd() della classe IssueAdder, fa uso dei metodi
beginTransaction() e commitTransaction() per delimitare la
transazione in corso, ed al suo interno utilizza il metodo
store() per memorizzare la nuova istanza di tipo Issue:
public
void setAdd( String projectId ) {
PersistenceBroker
broker = null;
try
{
broker
= PersistenceBrokerFactory.defaultPersistenceBroker();
broker.beginTransaction();
//L'utente
andrebbe preso dalla sessione
User
currentUser = getUser( "1" );
ProjectList
list = new ProjectList();
Project
currentProject = list.getProject( projectId );
broker.store(new
Issue(
currentProject, currentUser, summary, description, priority
));
broker.commitTransaction();
}
finally {
if
(broker != null) {
broker.close();
}
}
}
Ovviamente,
per fare in modo che la gestione delle transazioni funzioni
è necessario aver impostato l'attributo autoCommit
della connessione a 2, pena l'ottenimento di una eccezione.
Eliminazione
Per eliminare un oggetto persistito attraverso OJB è
necessario, all'interno di una transazione valida, chiamare
il metodo PersistenceBroker.delete():
PersistenceBroker
broker = null;
try {
broker
= PersistenceBrokerFactory.defaultPersistenceBroker();
broker.beginTransaction();
broker.delete(
issue );
broker.commitTransaction();
}
finally {
if
(broker != null) {
broker.close();
}
}
Librerie
OJB
Come in molti progetti open source di una certa dimensione,
OJB utilizza un numero notevole di librerie, ma quelle realmente
indispensabili sono sempre in numero inferiore a quelle distribuite
con i package di download. Per non rendere enorme il proprio
progetto è un'attività classica quella di spulciare
tra l'elenco per individuare quelle inutili. Quelle utilizzate
per eseguire questi esempi sono le seguenti (in grassetto
quelle importate da SUN JDO):
btree.jar
commons-beanutils.jar
commons-collections.jar
commons-dbcp-1.1.jar
commons-lang-2.0.jar
commons-logging.jar
commons-pool-1.1.jar
db-ojb-1.0.rc6.jar hsqldb.jar
jakarta-regexp-1.3.jar
jcs.jar
jdo.jar
jdori-enhancer.jar
jdori.jar
log4j-1.2.8.jar
mysql-connector-java-3.0.10-stable-bin.jar
ODMG
3.0
Un altro insieme di API per l'accesso agli oggetti persistenti
è quello definito dal gruppo di lavoro ODMG. L'Object
Data Management Group ha terminato il proprio lavoro nel 2001,
dopo 8 anni di attività. Ci lascia in eredità
la versione 3.0 delle specifiche, disponibili anche in forma
stampata come libro. Il testo descrive, oltre il modello informativo
generale, anche collegamenti con SmallTalk e C++, mentre la
parte Java è stata sottoposta al Java Community Process
ed utilizzata come base di JDO. Nonostante il fatto che le
ODMG saranno rese obsolete da JDO, rimangono specifiche importanti.
Si pensi però alla riconversione di conoscenze maturate
in ambito C++ su queste API: la loro presenza in strumenti
come Apache OJB può comunque risultare utile.
Nota: per funzionare correttamente, OJB richiede
che sia presente anche la libreria antlr-2.7.2.jar nel momento
in cui si utilizzino le API ODMG.
Accesso
ai dati
Le API ODMG prevedono, diversamente da altre API, l'utilizzo
di un oggetto Database, che rappresenta la base dati a cui
accedere, che deve essere aperto prima di qualsiasi operazione
e chiuso al termine del lavoro. Per accedere al database è
necessario ottenere un oggetto Implementation dalla classe
org.apache.ojb.odmg.OJB:
Implementation
impl = OJB.getInstance();
e
da questo ottenere il database da aprire:
db
= odmg.newDatabase();
db.open("default", Database.OPEN_READ_WRITE);
sullo
stesso oggetto Implementation da cui si è ottenuto
l'oggetto Database è possibile poi ottenere una transazione,
utilizzata per delimitare le operazioni:
Transaction
tx = impl.newTransaction();
tx.begin();
Gli
esempi presenti nei tutorial di OJB in questo ambito sono
un po' fuorvianti, in quanto i diversi pezzi di codice mostrati
danno ad intendere che sia possibile operare trasversalmente
al database, su istanze di Implementation diverse, e che il
Database aperto inizialmente sia disponibile in tutte queste.
In realtà, da una analisi del codice sorgente di OJB
risulta che è indispensabile utilizzare lo stesso oggetto
Implementation, in quanto le nuove istanze di questo oggetto
partono con un database corrente vuoto, condizione che genera
un'eccezione al primo tentativo di utilizzare l'implementazione
per eseguire una qualsivoglia operazione.
Elencare
i progetti
Per ottenere l'elenco dei progetti è necessario creare
una nuova query di tipo OQLQuery (Object Query Language) ed
impostarne il valore:
OQLQuery
query = impl.newOQLQuery();
query.create("select projects from " + Project.class.getName()
);
Come
si nota, OQL è simile ai linguaggi di query utilizzati
da Hibernate e Castor, utilizzando quindi un approccio differente
rispetto a PersistenceBroker API. L'esecuzione della query
restituisce una DList, che è una interfaccia definita
da ODMG che non è altro che una specializzazione di
una Collection/List Java che conterrà gli oggetti letti
dal database:
DList
results = (DList) query.execute();
Recupero
di un singolo oggetto
Per ottenere un singolo oggetto da una collezione è
necessario specificare una condizione di WHERE nella query
OQL, utilizzando come segnaposto dei valori identificatori
come $1, $2 ... $n. Ciascuno di questi parametri dovrà
poi essere valorizzato tramite il metodo bind() dell'interfaccia
OQLQuery:
OQLQuery
query = impl.newOQLQuery();
query.create("select projects from " + Project.class.getName()
+ " where id = $1");
query.bind(projectId);
Il
risultato è sempre un oggetto DList, dal quale è
possibile ottenere il primo valore utilizzando il suo iteratore:
DList
results = (DList) query.execute();
Project project = (Project) results.iterator().next();
Aggiunta
di una segnalazione
L'implementazione dell'inserimento di nuovi oggetti nella
base dati avviene attraverso il metodo lock() dell'interfaccia
Transaction, che consente di aggiungere o aggiornare un oggetto
già presente sulla base dati. Nel codice seguente si
vede l'organizzazione di una parte della classe IssueAdder,
che implementa l'aggiunta di una nuova segnalazione tramite
il metodo setAdd(). Questo si basa a sua volta sui metodi
di utilità open() e close() che si occupano di aprire
e chiudere la connessione con il database:
public
void setAdd( String projectId ) throws ODMGException {
//L'utente andrebbe preso dalla sessione
User
currentUser = getUser( "1" );
ProjectList
list = new ProjectList();
Project
currentProject = list.getProject( projectId );
Issue
issue = new Issue(currentProject, currentUser, summary, description,
priority);
Implementation
impl = OJB.getInstance();
open(
impl );
Transaction
tx = impl.newTransaction();
tx.begin();
tx.lock(issue,
Transaction.WRITE);
tx.commit();
close();
}
void
open(Implementation odmg) throws ODMGException {
db
= odmg.newDatabase();
db.open("default",
Database.OPEN_READ_WRITE);
}
void
close() throws ODMGException {
db.close();
}
Tabella 2 - Metodi dell'interfaccia Transaction
Cancellazione
L'eliminazione di oggetti persistiti, avviene in ODMG non
attraverso l'interfaccia Transaction, ma utilizzando il Database.
Nonostante questo, una transazione deve essere in corso perchè
la chiamata abbia successo. Nell'esempio seguente viene ottenuta
una implementazione dall'oggetto OJB che viene poi aperta;
viene poi ottenuta una nuova transazione che viene avviata.
Viene poi ottenuto il database che contiene l'oggetto project
(che nell'attuale implementazione di OJB ritorna semplicemente
il database di default). Su questo viene chiamato il metodo
deletePersistent(), indicando l'istanza da eliminare:
Implementation
impl = OJB.getInstance();
Database db = odmg.newDatabase();
db.open("default", Database.OPEN_READ_WRITE);
Transaction
tx = impl.newTransaction();
tx.begin();
Database
db1 = impl.getDatabase( project );
db1.deletePersistent( project );
tx.commit();
La
transazione viene poi conclusa con una chiamata a commit().
Una
terza API: JDO
Java Data Objects è la tecnologia di SUN per la persistenza
trasparente degli oggetti, da qualche mese (settembre 2003),
rilasciata in versione 1.0.1. E' prevedibile che i diversi
strumenti di persistenza come Hibernate, Castor, OJB ed altri
supporteranno queste API, ed una implementazione preliminare
è già presente in OJB. Allo stato attuale però
questa non è completa, ed il pieno supporto è
previsto per la versione 2.0 di OJB. Nella prossima major
release dello strumento di Apache, dunque, sarà disponibile
una terza API di accesso ai dati, questa volta pienamente
standard SUN.
Ad ogni modo, è possibile utilizzare OJB in congiunzione
con la reference implementation di JDO offerta da SUN allo
scopo di sperimentarne le funzionalità congiuntamente.
Le librerie JDO richieste dovrebbero già essere state
installate in fase di configurazione di OJB e sono jdo.jar
e jdori.jar. Una trattazione della tecnologia JDO sarà
oggetto del prossimo articolo.
Conclusioni
Object Relational Bridge è uno strumento potente ma
complesso, che necessita di un po' di tempo per poter essere
digerito; anche l'implementazione delle semplici funzionalità
descritte in questo articolo ha costretto l'autore a scavare
nel codice sorgente per scoprirne il funzionamento anche grazie
ad istruzioni di logging.
La disponibilità di ben tre API di accesso (anche se
JDO non sarà completa prima della versione 2.0) rende
OJB lo strumento più flessibile tra quelli visti finora,
anche se al prezzo di una certa complessità.
Bibliografia
[1] Massimiliano Bigatti - "Motori di persistenza in
Java: parte I, Hibernate", Mokabyte, marzo 2004
[2] Massimiliano Bigatti - "Motori di persistenza in
Java: parte II, Castor", Mokabyte, aprile 2004
[3] Massimiliano Bigatti - "Motori di persistenza in
Java: parte III, Apache OJB", Mokabyte, maggio 2004
|