MokaByte 86 - Giugno 2004 
Motori di persistenza in Java
IV parte: OJB tramite PersistenceBroker ed ODMG
di
Massimiliano Bigatti

PersistenceBroker è il nome delle API a basso livello native di Object Relational Bridge che è possibile utilizzare per inserire, modificare e cancellare informazioni dal database attraverso il prodotto di Apache. Su queste API sono state costruite implementazioni per altri standard come quello di ODMG.

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


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