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
|