MokaByte 85 - Maggio 2004 
Motori di persistenza in Java
III parte: OJB
di
Massimiliano
Bigatti
Non poteva certo mancare, all'interno del panorama dei framework di persistenza in Java una iniziativa di Apache Group, che sembra estendersi sempre di più come numero e qualità di progetti ma, come vedremo, si caratterizza spesso per complessità

Il framework Object Relational Bridge di Apache (OJB) è un progetto presente nel gruppo "database", che è disponibile sul sito http://db.apache.org. L'iniziativa è ancora un po' agli inizi, visto che racchiude solo una manciata di progetti.
OJB è, come i cugini Hibernate e Castor, un framework per la persistenza trasparente di POJO che offre però una serie di caratteristiche interessanti. La versione provata è la 1.0 rc6, dovrebbe essere quindi una versione abbastanza stabile e vicina alla fatidica 1.0.
La particolarità di OJB che non si ritrova in tutti i framework di persistenza è la possibilità di interagire con il database e gli oggetti non solo attraverso le API native del prodotto, in questo caso è il PersistenceBroker, ma anche attraverso API standard, quali quelle di ODMG e di SUN (JDO). Si noti però che per far funzionare quest'ultimo è necessario comunque installare il RI di SUN per JDO.

 

Funzionalità avanzate
Alcune caratteristiche che la documentazione di OJB dichiara di possedere il prodotto sono interessanti: la scalabilità è garantita dai sistemi embedded (J2ME), fino alle applicazioni standalone ed alle applicazioni server basate su J2EE. Per quanto riguarda quest'ultima tecnologia, OJB è utilizzabile con JNDI per l'ottenimento delle fonti dati e fornisce integrazione con JTA e JCA. Ovviamente può essere utilizzato con JSP, Servlet e bean di sessione, ed offre un supporto speciale per i bean di entità di tipo BMP.
Implementa cache di oggetti abbastanza evolute e la materializzazione di oggetti incompleti (lazy loading) attraverso proxy virtuali. Supporta gli ambienti in cluster ed offre di conseguenza un sistema di locking distribuito con livelli di isolamento della transazione configurabili.
Tutte queste funzionalità vengono però ad un prezzo, in quanto la struttura di OJB sembra molto pesante, ed anche le operazioni necessarie per partire con un semplice progetto sono più complesse dei framework visti in precedenza. In particolare, per poter partire da una situazione pulita è indispensabile installare la RI di JDO e generare il target ojb-blank con il comando:

ant ojb-blank

Verrà prodotto, dopo l'esecuzione di uno script di Ant lungo e complesso, il file ojb-blank.jar che contiene un progetto vuoto pronto per poter essere esteso con le funzionalità specifiche dell'applicazione o per essere integrato nel proprio progetto.

 

Configuration Repository
La configurazione del prodotto avviene, come per gli altri framework di persistenza, attraverso file XML. Questa volta non sono però organizzati uno per classe, ma uno per categoria di informazione contenuta. C'è poi un file principale, repository.xml, che include tutti gli altri. Alcuni file secondari sono:

repository_database.xml;
repository_ejb.xml;
repository_jdo.xml;
repository_user.xml

Questi file sono contenuti in ojb-blank.jar/src/resources/ insieme ad altri file di configurazione. Avendo già una struttura di progetto a disposizione (anche in questa puntata verrà presentata una versione di mokatrack, questa volta utilizzando OJB per la persistenza), questi file di configurazione verranno importati, insieme alle librerie disponibili, nella struttura di progetto utilizzata nelle puntate precedenti [1] e [2].

 

Configurazione del database
OJB viene distribuito configurato per l'utilizzo di HSQL, il database SQL open source full Java che può essere incorporato nelle applicazioni, ma mokatrack dispone già di una base dati, basata su MySQL. Tra l'altro è interessante vedere se OJB è in grado di accedere allo stesso schema con il quale Hibernate e Castor hanno interagito senza problemi. La configurazione del database può essere fatta a mano o con l'ausilio dello script di Ant che produce il file ojb-blank.jar; nel secondo caso è necessario modificare il file build.properties in modo da specificare tipo e parametri del database da utilizzare.
Per modificare la configurazione a mano è invece sufficiente inserire il seguente descrittore nel file
repository_database.xml:

<jdbc-connection-descriptor
jcd-alias="default"
default-connection="true"
platform="MySQL"
jdbc-level="2.0"
driver="org.gjt.mm.mysql.Driver"
protocol="jdbc"
subprotocol="mysql"
dbalias="//localhost:3306/mokatrack"
username="root"
password="****"
useAutoCommit="2"
>
<object-cache class="org.apache.ojb.broker.cache.ObjectCacheDefaultImpl">
<attribute attribute-name="timeout" attribute-value="900"/>
<attribute attribute-name="autoSync" attribute-value="true"/>
</object-cache>

<connection-pool maxActive="21" validationQuery="" />

<sequence-manager className="org.apache.ojb.broker.util.sequence.SequenceManagerMySQLImpl">
</sequence-manager>
</jdbc-connection-descriptor>

I parametri di connessione al database sono i consueti ma meritano particolare attenzione:
default-connection; OJB utilizza per default la connessione che, tra tutte quelle definite, possiede questa proprietà impostata a true. Per questo motivo è necessario verificare che tutte le altre connessioni presente nel file (sono alcune visto che sono presenti anche le connessioni di prova utilizzate dai test case del prodotto) siano impostate a false;
useAutoCommit; indica se impostare l'autocommit. La cosa strana è che il codice si blocca se non è definito questo attributo, in quanto tenta di eseguire un parseInt() di una stringa nulla, e che purtroppo l'attributo manca anche nelle connessioni già presenti nel file. E' necessario dunque assicurarsi che questo attributo sia specificato in tutte le connessioni impostate, anche quelle non legate alla nostra applicazione. I valori possibili sono:

0 - mantiene l'impostazione di autoCommit della connessione utilizzata,
1 - forza l'autoCommit a true,
2 - forza l'autoCommit a false.

La parte relativa ad object-cache controlla i parametri della cache degli oggetti, mentre quella relativa al connection-pool permette di specificare il massimo numero di connessioni attive, impostate a 21. Questi parametri sono quelli di default di OJB.
L'impostazione del sequence-manager non è stata invece mutuata dalla configurazione standard: per default OJB utilizza un proprio algoritmo interno, ma con MySQL è possibile utilizzare i progressivi nativi, supportati (ancora in versione non definitiva), dalla classe SequenceManagerMySQLImpl.

 

Configurazione delle entità
Le entità sono configurate nel file repository_user.xml e sono le seguenti:

<class-descriptor class="com.mokabyte.tracking.model.Project" table="projects">
<field-descriptor name="id" column="id" jdbc-type="BIGINT" primarykey="true"
                  autoincrement="true" />
<field-descriptor name="key" column="ukey" jdbc-type="VARCHAR" />
<field-descriptor name="description" column="description" jdbc-type="VARCHAR" />
<field-descriptor name="site" column="site" jdbc-type="VARCHAR" />
<field-descriptor name="leadId" column="id_lead" jdbc-type="BIGINT" />

<reference-descriptor
name="lead"
class-ref="com.mokabyte.tracking.model.User"
auto-retrieve="true">
<foreignkey field-ref="leadId"/>
</reference-descriptor>

<collection-descriptor name="issues"
element-class-ref="com.mokabyte.tracking.model.Issue"
orderby="ukey"
sort="ASC"
auto-retrieve="true"
auto-update="false"
auto-delete="true">
<inverse-foreignkey field-ref="projectId"/>
</collection-descriptor>

</class-descriptor>

<class-descriptor class="com.mokabyte.tracking.model.User" table="users">
<field-descriptor name="id" column="id" jdbc-type="BIGINT"
                 
primarykey="true" autoincrement="true"
/>
<field-descriptor name="account" column="account" jdbc-type="VARCHAR" />
<field-descriptor name="password" column="password" jdbc-type="VARCHAR" />
<field-descriptor name="name" column="name" jdbc-type="VARCHAR" />
<field-descriptor name="email" column="email" jdbc-type="VARCHAR" />
</class-descriptor>

<class-descriptor class="com.mokabyte.tracking.model.Issue" table="issues">
<field-descriptor name="id" column="id" jdbc-type="BIGINT"
                  primarykey="true" autoincrement="true" />

<!-- richiesto per il mapping di Project -->
<field-descriptor name="projectId" column="id_project"
                  jdbc-type="BIGINT" access="anonymous" />

<!-- relazione inversa -->
<reference-descriptor
name="project"
class-ref="com.mokabyte.tracking.model.Project"
auto-retrieve="true">
<foreignkey field-ref="projectId"/>
</reference-descriptor>

<field-descriptor name="key" column="ukey" jdbc-type="VARCHAR" />
<field-descriptor name="state" column="state" jdbc-type="INTEGER" />
<field-descriptor name="priority" column="priority" jdbc-type="INTEGER" />
<field-descriptor name="summary" column="summary" jdbc-type="VARCHAR" />
<field-descriptor name="description" column="description" jdbc-type="VARCHAR" />

<field-descriptor name="assegneeId" column="assegnee"
                  jdbc-type="BIGINT" access="anonymous" />
<reference-descriptor
name="assegnee"
class-ref="com.mokabyte.tracking.model.User"
auto-retrieve="true">
<foreignkey field-ref="assegneeId"/>
</reference-descriptor>

<field-descriptor name="reporterId" column="reporter"
                  jdbc-type="BIGINT" access="anonymous" />
<reference-descriptor name="reporter" class-ref="com.mokabyte.tracking.model.User"
                      auto-retrieve="true">
<foreignkey field-ref="reporterId"/>
</reference-descriptor>

<!-- impostato a bit per ottenere un valore compatibile con boolean -->
<field-descriptor name="resolved" column="resolved" jdbc-type="BIT" />

<field-descriptor name="created" column="created" jdbc-type="DATE" />
<field-descriptor name="updated" column="updated" jdbc-type="DATE" />
<field-descriptor name="votes" column="votes" jdbc-type="INTEGER" />
</class-descriptor>

Si noti che tutte le chiavi sono configurate come BIGINT. OJB è più rigido rispetto ad altri framework di persistenza: la corrispondenza tra tipo Java e JDBC è infatti fissa e non configurabile come ad esempio con Hibernate. Le classi che implementano il modello ad oggetti di mokatrack (ereditate dalle precedenti versioni del programma), utilizzavano chiavi di tipo long. Questo tipo di dato è mappato da OJB su BIGINT; il tipo int è invece mappato su INTEGER;

 

Configurazione delle relazioni
Per ciascuna relazione 1:1 (o di lookup), ad esempio nell'entità Project che contiene un attributo di tipo User, è necessario specificare un campo (o di più, in funzione della chiave primaria) con la chiave da utilizzare per accedere alla tabella collegata. E' il caso, nella definizione della classe Project dell'elemento:

<field-descriptor name="leadId" column="id_lead" jdbc-type="BIGINT" />

Il campo leadId, aggiunto nella classe Project in questa versione, viene poi utilizzato dall'elemento reference-descriptor seguente, che lo utilizza come chiave (elemento foreignkey) per accedere alla tabella Utenti.

<reference-descriptor name="lead" class-ref="com.mokabyte.tracking.model.User"
                      auto-retrieve="true">
<foreignkey field-ref="leadId"/>
</reference-descriptor>

Gli altri elementi di configurazione sono:

  • name. Indica il nome della proprietà nella classe Project che dovrà ricevere il dato;
  • class-ref. Specifica la classe dell'elemento collegato (User);
  • auto-retrieve. Se impostato a true indica ad OJB di recuperare l'oggetto collegato al momento del recupero dell'oggetto principale. Se impostato a false, il recupero dovrà essere eseguito manualmente con le API di OJB.

Nella classe Project è stato aggiunto l'attributo leadId, anche se questa operazione non è indispensabile; OJB permette di di specificare l'attributo access che, se impostato ad anonymous, indica di non provare a valorizzare l'attributo relativo nella classe. Per questo motivo si può omettere di specificare la proprietà nella classe. E' il caso dei due campi assegneeId e reporterId della classe Issue:

<field-descriptor name="assegneeId" column="assegnee" jdbc-type="BIGINT"
                  access="anonymous" />
<field-descriptor name="reporterId" column="reporter" jdbc-type="BIGINT"
                  access="anonymous" />

 

Relazioni 1:n
Le relazioni in cascata in Mokatrack sono utilizzate per collegare le segnalazioni ad un singolo progetto e sono configurate dall'elemento collection-descriptor definito nella classe Project:

<collection-descriptor name="issues"
element-class-ref="com.mokabyte.tracking.model.Issue"
orderby="ukey"
sort="ASC"
auto-retrieve="true"
auto-update="false"
auto-delete="true">
<inverse-foreignkey field-ref="projectId"/>
</collection-descriptor>

Gli attributi da impostare sono:

  • name. Nome dell'attributo presente nella classe Project che dovrà ospitare la collezione;
  • element-class-ref. Nome della classe degli oggetti presenti nella collezione;
  • orderby. Campo sul quale eseguire l'ordinamento;
  • sort. ASC | DESC indica la direzione di ordinamento;
  • auto-retrieve. Vedi paragrafo precedente;
  • auto-update. Aggiorna automaticamente gli oggetti figli alla modifica di quello principale;
  • auto-delete. Elimina automaticamente gli oggetti figli all'eliminazione dell'oggetto principale

 

Relazioni inverse
La classe Issue ha la particolarità di possedere un riferimento di tipo Project al progetto a cui appartiene. E' necessario dunque implementare una relazione inversa nella definizione di Issue in modo che alla materializzazione di ogni istanza di Issue venga impostato il corretto oggetto Project. Grazie alla presenza della cache di oggetti, tutte queste operazioni non risulteranno in una ulteriore query al database:


<!-- richiesto per il mapping di Project -->
<field-descriptor name="projectId" column="id_project"
                  jdbc-type="BIGINT" access="anonymous" />

<!-- relazione inversa -->
<reference-descriptor name="project" class-ref="com.mokabyte.tracking.model.Project"
                      auto-retrieve="true">
<foreignkey field-ref="projectId"/>
</reference-descriptor>

 

Altre configurazioni
Gli altri file di configurazione non necessitano di interventi anche se contengono una serie di informazioni inutili, messe per agevolare i neofiti all'individuazione dei punti da modificare. In particolare, nel progetto mokatrack si trovano i seguenti file, svuotati del loro contenuto rispetto agli originali estratti da ojb-blank:

repository_ejb.xml
repository_junit_metaseq.xml
repository_junit_model.xml
repository_junit_odmg.xml
repository_junit_otm.xml
repository_junit_reference.xml
repository_junit.xml

Un altro file importante è OJB.properties, che contiene le diverse configurazioni del prodotto, ed anche la tipologia di logging ed i livelli di output da utilizzare. Sebbene sia presente Log4J e Commons-Logging, infatti, OJB impone un ulteriore livello di indirezione attraverso una sua factory di logging, pilotata dalla chiave LoggerClass che per default vale org.apache.ojb.broker.util.logging.PoorMansLoggerImpl, l'implementazione di logging integrata in OJB. Non ci si deve dunque far fuorviare dalla presenza di log4.jar e log4j.properties: il file a cui accedere per attivare il logging è sempre OJB.properties.
Un'altra chiave di configurazione importante è PersistentFieldClass. Ad un certo punto si è notato che le System.out poste nel modello di mokatrack non producevano alcun output; dopo qualche ricerca è risultato che questa configurazione indica la politica di accesso alle proprietà del modello. Per default OJB utilizza una politica ad accesso diretto degli attributi, saltando i mutatori/accessori (accessor/mutator). Per fare in modo che questi siano utilizzati, è necessario impostare PersistentFieldClass a
org.apache.ojb.broker.metadata.fieldaccess.PersistentFieldIntrospectorImpl.

 

To be continued...
Fino ad ora abbiamo configurato Apache OJB, nella prossima puntata utilizzeremo le API native ed ODMG per l'accesso ai dati attraverso questo strumento.

 

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

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