Introduzione
Oggigiorno è raro trovare un'applicazione che
non abbia bisogno di immagazzinare dati persistenti
in qualche modo, su file nei casi più semplici
fino ad arrivare ai più sofisticati Database
Management Systems (DBMS). Quando
si scrivono applicazioni con database ci si trova di
fronte a due possibili scenari: uno in cui il database
esiste già (legacy) e uno in cui va progettato
da zero. Nel primo caso si può procedere con
un reverse engineering per identificare un modello dei
dati a partire dalle tabelle esistenti. Nel secondo
caso l'approccio tradizionale consiste nell'identificare
entità e relazioni coinvolte e creare le tabelle
sul database. In entrambi i casi si parte dalla definizione
dei dati e vi si costruisce sopra l'applicazione.
Questo
approccio, indubbiamente immediato quando si usano strumenti
di sviluppo RAD (Rapid Application Development), diviene
immediatamente svantaggioso con metodologie di sviluppo
più sofisticate, in quanto vincola le entità
del modello dati (tipicamente ad oggetti con un linguaggio
come Java) alla struttura relazionale tipica dei tradizionali
database. I due modelli, quello relazionale e quello
ad oggetti, sono fortemente diversi per cui è
necessario escogitare una forma di "traduzione"
da un modello all'altro, detta Object/Relational Mapping
(ORM). Questa traduzione è sempre necessaria
ed introduce uno strato aggiuntivo di logica, con tutto
ciò che ne consegue in termini di sviluppo, debugging,
manutenzione. Inoltre l'applicazione conserverà
inevitabilmente la natura relazionale del modello di
partenza, risultando strutturata prevalentemente su
tale modello piuttosto che sul paradigma di sviluppo
ad oggetti, con lo sviluppatore sempre costretto a fare
i conti con una traduzione più o meno esplicita
da un modello all'altro.
Persistenza
con Hibernate
Esistono
diversi strumenti per agevolare lo sviluppo di applicazioni
con dati persistenti, strumenti che variano da piattaforma
a
piattaforma. Uno dei più diffusi è il
motore di persistenza Hibernate.
Supponendo
di lavorare con la metodologia appena descritta, avremo
sostanzialmente tre fasi di progettazione:
1.
definizione del modello dati relazionale,
2. definizione del modello ad oggetti e
3. definizione del mapping fra l'uno e l'altro.
In
questo scenario, una volta progettato il modello relazionale
dei dati, Hibernate può prendersi carico di tradurlo
e mapparlo in
oggetti, rendendo trasparente il processo. Partendo
da un database legacy Hibernate è in grado di
mappare le tabelle in classi: ogni riga della tabella
sarà un'istanza della classe corrispondente.
Lo sviluppatore non dovrà più occuparsi
di interagire con il DBMS per
mezzo di JDBC, ma userà la più familiare
forma degli oggetti e le comuni strutture dati del linguaggio.
/*
usando JDBC */
ResultSet rs = stmt.executeQuery("SELECT * FROM
TABELLA");
while (rs.next()) {
String s = rs.getString("CAMPO1");
float n = rs.getFloat("CAMPO2");
System.out.println(s + " " + n);
}
/*
con Hibernate */
List righe = session.createQuery("from Tabella").list();
for (Iterator i = righe.iterator(); i.hasNext();) {
Tabella riga = (Tabella) i.next();
System.out.println(riga.getCampo1 + " " +
riga.getCampo2);
}
Ciò
non deve però trarre in inganno. Nonostante l'indubbio
vantaggio di usare oggetti anziché statement
SQL inviati tramite JDBC e l'onere di gestire istanze
di ResultSet, non ci troviamo di fronte ad un modello
Object Oriented, bensì ad un modello relazionale
in cui le tabelle appaiono in forma di oggetti: per
lavorare con un modello realmente ad oggetti è
sempre necessario introdurre un ulteriore
strato di traduzione tra oggetti-tabella e oggetti-modello,
con un costo sia in termini di tempo che in termini
di codice da mantenere.
L'approccio
a oggetti
Hibernate
permette un approccio alla persistenza completamente
diverso da quello tradizionale, un approccio totalmente
orientato agli oggetti e trasparente allo sviluppatore.
In
un'applicazione Java ben progettata troveremo tipicamente
tre componenti distinte: un modello che rappresenta
le entità manipolate
dall'applicazione; alcune viste del modello, che presentano
il modello all'utente in una forma a lui leggibile;
un controllore, che si occupa di gestire l'input dell'utente,
esaudirne le richieste (delegando al modello ove necessario),
interagire con il database e delegare alle viste la
presentazione dei risultati. Esaminiamo
dunque il modello, la componente che ci interessa in
questa sede. Il modello è un'astrazione delle
entità che la nostra applicazione deve gestire.
Il paradigma ad oggetti permette una modellazione ricca
e strutturata degli oggetti del mondo reale che dobbiamo
rappresentare nell'applicazione, fornendo costrutti
come ereditarietà e associazioni, pattern, ecc.
non direttamente rappresentabili con un modello relazionale.
In questa ottica risulta quindi più naturale
progettare un modello puramente ad oggetti, direttamente
implementabile in Java, e a partire da esso costruire
il database.
Hibernate
permette di sviluppare questo approccio in modo diretto
ed elegante, consentendo di concentrare gli sforzi esclusivamente
sulla modellazione ad oggetti e gestendo tutte le problematiche
di persistenza autonomamente ed in maniera del tutto
trasparente. In
questo modo è possibile progettare il modello
e considerare questi oggetti come "persistenti",
dimenticandosi completamente
dell'esistenza del database!
Modellazione
e metainformazioni
Supponiamo
di dover gestire una semplice anagrafica di clienti,
che possono essere sia persone che aziende. Ogni soggetto
avrà le sue informazioni anagrafiche e amministrative,
oltre a vari indirizzi e numeri di telefono. Il modello
potrebbe essere espresso come nel
seguente diagramma:
Figura 1 - Il modello dati
Abbiamo
il Soggetto, che può rappresentare appunto l'astrazione
del soggetto anagrafico ed incarnare il concetto di
azienda; abbiamo poi una sua generalizzazione, Persona,
che ne eredita attributi e comportamenti aggiungendone
di nuovi; infine abbiamo la classe
Indirizzo.
Si
possono notare alcune cose. Innanzitutto Soggetto possiede
un attributo multivalore, numeroTelefono: un soggetto
può avere da 0 a n numeri. Inoltre abbiamo modellato
l'associazione fra Soggetto ed Indirizzo come composizione,
in quanto l'indirizzo non può esistere se non
associato ad un soggetto. Persona eredita da Soggetto
ogni metodo e proprietà, comprese quindi la proprietà
numeriTelefono e la composizione con Indirizzo.
Il
vantaggio immediato di un simile modello è che
può essere implementato direttamente in Java,
inserendo normalmente tutti i
metodi e gli attributi necessari.
/*
listato Java del modello */
public class Soggetto {
private Long id;
private String partitaIva;
private String cognomeRagioneSociale;
private Set indirizzi;
private Set numeriTelefono;
/** Costruttore di default. */
public Soggetto() {
this(null);
}
/** Costruttore con id. */
public Soggetto(final Long id) {
this.id = id;
indirizzi = new HashSet();
numeriTelefono = new HashSet();
}
public Long getId() {
return this.id;
}
public void setId(final Long id) {
this.id = id;
}
public String getPartitaIva() {
return this.partitaIva;
}
public void setPartitaIva(final String partitaIva) {
this.partitaIva = partitaIva;
}
/**
* Il cognome per una persona, la ragione sociale per
un'azienda, ecc.
*/
public String getCognomeRagioneSociale() {
return this.cognomeRagioneSociale;
}
public void setCognomeRagioneSociale(String name) {
this.cognomeRagioneSociale = name;
}
public Set getIndirizzi() {
return this.indirizzi;
}
public void setIndirizzi(final Set indirizzi) {
this.indirizzi = indirizzi;
}
public Set getNumeriTelefono() {
return this.numeriTelefono;
}
public void setNumeriTelefono(final Set numeri) {
this.numeriTelefono = numeri;
}
public void addIndirizzo(final Indirizzo indirizzo)
{
indirizzi.add(indirizzo);
}
public void removeIndirizzo(final Indirizzo indirizzo)
{
indirizzi.remove(indirizzo);
}
public void addTelefono(final String telefono) {
numeriTelefono.add(telefono);
}
public void removeTelefono(final String telefono) {
numeriTelefono.remove(telefono);
}
}
public class Persona extends Soggetto {
private Date dataNascita;
private String nomeProprio;
private String genere;
/** Costruttore di default. */
public Persona() {
super();
}
/**
* La data di nascita.
*/
public Date getDataNascita() {
return this.dataNascita;
}
public void setDataNascita(final Date data) {
this.dataNascita = data;
}
/**
* Il nome proprio della persona.
*/
public String getNomeProprio() {
return this.nomeProprio;
}
public void setNomeProprio(final String nome) {
this.nomeProprio = nome;
}
/**
* Il genere, volgarmente detto sesso, che sarà
"M" per il genere
* maschile ed "F" per quello femminile.
*/
public String getGenere() {
return this.genere;
}
public void setGenere(final String genere) {
this.genere = genere;
}
}
public class Indirizzo {
private String indirizzo;
private String comune;
private String localita;
private String nome;
private String provincia;
private String cap;
/** Costruttore di default. */
public Indirizzo() {
indirizzo = null;
comune = null;
localita = null;
nome = null;
provincia = null;
cap = null;
}
/**
* Restituisce l'indirizzo, p.es. "via Roma, 23".
*/
public String getIndirizzo() {
return this.indirizzo;
}
/**
* Imposta l'indirizzo, p.es. "via Roma, 23".
*/
public void setIndirizzo(final String indirizzo) {
this.indirizzo = indirizzo;
}
/**
* Il comune.
*/
public String getComune() {
return this.comune;
}
public void setComune(final String comune) {
this.comune = comune;
}
/**
* La località.
*/
public String getLocalita() {
return this.localita;
}
public void setLocalita(final String localita) {
this.localita = localita;
}
/**
* Nome identificativo per l'indirizzo, p.es "abitazione",
"ufficio", ecc.
*/
public String getNome() {
return this.nome;
}
public void setNome(final String nome) {
this.nome = nome;
}
/**
* La provincia.
*/
public String getProvincia() {
return this.provincia;
}
public void setProvincia(final String provincia) {
this.provincia = provincia;
}
/**
* Il CAP (Codice Avviamento Postale).
*/
public String getCap() {
return this.cap;
}
public void setCap(final String cap) {
this.cap = cap;
}
}
Abbiamo
quindi tre classi, ognuna con i suoi attributi privati
e i suoi metodi accessori. In particolare abbiamo aggiunto
a Soggetto dei metodi per aggiungere ed eliminare facilmente
indirizzi e numeri di telefono (add/removeIndirizzo(),
ecc.). Sarebbe possibile aggiungere ulteriori metodi
e controlli (che abbiamo omesso per brevità),
per esempio un metodo per calcolare l'età di
una persona inbase alla data di nascita, o un controllo
sulla correttezza del codice fiscale nel metodo setCodiceFiscale().
Così
com'è abbiamo quindi un buon modello ad oggetti,
funzionale alla nostra applicazione e facilmente gestibile
dallo sviluppatore, ma non ancora persistente. Per renderlo
tale dobbiamo fornire ad Hibernate le informazioni necessarie
per poterlo gestire in modo appropriato. Per fare ciò
abbiamo due strade: scrivere esplicitamente dei file
XML contenenti le informazioni di mapping oppure fornire
queste informazioni direttamente nei sorgenti del modello
e lasciare che Hibernate generi questi file al posto
nostro. Il secondo approccio è preferibile in
quanto libera dall'onere di scrivere esplicitamente
file di configurazione aggiuntivi: avremo così
un solo sorgente che implementa direttamente un modello
persistente.
Figura 2 - Il modello dati... persistente!
XDoclet
è lo strumento che ci permette di inserire le
informazioni di persistenza direttamente nei sorgenti.
XDoclet mette a disposizione alcuni tag, interpretabili
da Hibernate, da inserire nei commenti Javadoc (documentate
i vostri sorgenti, vero?): questi tag, trovandosi nei
commenti, vengono ignorati dal compilatore Java, ma
sono visti e utilizzati profiquamente dagli stumenti
di Hibernate. Questi tag permettono di definire quali
classi e quali attributi dell'oggetto devono essere
immagazzinati in un database e come. I sorgenti così
corredati sono quindi processati dagli strumenti di
Hibernate, i quali si preoccupano di creare automaticamente
lo schema del database e i file di mapping. Aggiungiamo
ai nostri sorgenti i tag XDoclet.
/*
sorgenti con xdoclet */
/**
* Soggetto.
*
* @hibernate.class
*/
public class Soggetto {
private Long id;
private String partitaIva;
private String cognomeRagioneSociale;
private Set indirizzi;
private Set numeriTelefono;
/** Costruttore di default. */
public Soggetto() {
this(null);
}
/** Costruttore con id. */
public Soggetto(final Long id) {
this.id = id;
indirizzi = new HashSet();
numeriTelefono = new HashSet();
}
/**
* @hibernate.id
* generator-class="native"
*/
public Long getId() {
return this.id;
}
public void setId(final Long id) {
this.id = id;
}
/**
* @hibernate.property
* length="16"
* unique="true"
*/
public String getPartitaIva() {
return this.partitaIva;
}
public void setPartitaIva(final String partitaIva) {
this.partitaIva = partitaIva;
}
/**
* Il cognome per una persona, la ragione sociale per
un'azienda, ecc.
*
* @hibernate.property length="60"
*/
public String getCognomeRagioneSociale() {
return this.cognomeRagioneSociale;
}
public void setCognomeRagioneSociale(String name) {
this.cognomeRagioneSociale = name;
}
/**
* @hibernate.set
* name="indirizzi"
* @hibernate.collection-key
* column="id"
* @hibernate.collection-composite-element
* class="articolo.Address"
*/
public Set getIndirizzi() {
return this.indirizzi;
}
public void setIndirizzi(final Set indirizzi) {
this.indirizzi = indirizzi;
}
/**
* @hibernate.set
* table="numeri_telefono"
* @hibernate.collection-key
* column="id"
* @hibernate.collection-element
* column="telefono"
* type="string"
* not-null="true"
*/
public Set getNumeriTelefono() {
return this.numeriTelefono;
}
public void setNumeriTelefono(final Set numeri) {
this.numeriTelefono = numeri;
}
public void addIndirizzo(final Indirizzo indirizzo)
{
indirizzi.add(indirizzo);
}
public void removeIndirizzo(final Indirizzo indirizzo)
{
indirizzi.remove(indirizzo);
}
public void addTelefono(final String telefono) {
numeriTelefono.add(telefono);
}
public void removeTelefono(final String telefono) {
numeriTelefono.remove(telefono);
}
}
/**
* Persona.
*
* @hibernate.joined-subclass
* @hibernate.joined-subclass-key
* column="id"
*/
public class Persona extends Soggetto {
private Date dataNascita;
private String nomeProprio;
private String genere;
/** Costruttore di default. */
public Persona() {
super();
}
/**
* La data di nascita.
*
* @hibernate.property
*/
public Date getDataNascita() {
return this.dataNascita;
}
public void setDataNascita(final Date data) {
this.dataNascita = data;
}
/**
* Il nome proprio della persona.
*
* @hibernate.property
* length="30"
*/
public String getNomeProprio() {
return this.nomeProprio;
}
public void setNomeProprio(final String nome) {
this.nomeProprio = nome;
}
/**
* Il genere, volgarmente detto sesso, che sarà
"M" per il genere maschile ed "F"
per quello
* femminile.
*
* @hibernate.property
* length="1"
*/
public String getGenere() {
return this.genere;
}
public void setGenere(final String genere) {
this.genere = genere;
}
}
/**
* Indirizzo.
*/
public class Indirizzo {
private String indirizzo;
private String comune;
private String localita;
private String nome;
private String provincia;
private String cap;
/** Costruttore di default. */
public Indirizzo() {
indirizzo = null;
comune = null;
localita = null;
nome = null;
provincia = null;
cap = null;
}
/**
* Restituisce l'indirizzo, p.es. "via Roma, 23".
* @return Una stringa contenente la via, la piazza,
ecc.
*
* @hibernate.property
* length="255"
*/
public String getIndirizzo() {
return this.indirizzo;
}
/**
* Imposta l'indirizzo, p.es. "via Roma, 23".
* @param indirizzo Una stringa contenente la via, la
piazza, ecc.
*/
public void setIndirizzo(final String indirizzo) {
this.indirizzo = indirizzo;
}
/**
* Il comune.
*
* @hibernate.property
* length="60"
*/
public String getComune() {
return this.comune;
}
public void setComune(final String comune) {
this.comune = comune;
}
/**
* La località.
*
* @hibernate.property
* length="255"
*/
public String getLocalita() {
return this.localita;
}
public void setLocalita(final String localita) {
this.localita = localita;
}
/**
* Nome identificativo per l'indirizzo, p.es "abitazione",
"ufficio", ecc.
*
* @hibernate.property
* length="30"
* not-null="true"
*/
public String getNome() {
return this.nome;
}
public void setNome(final String nome) {
this.nome = nome;
}
/**
* La provincia.
*
* @hibernate.property
* length="60"
*/
public String getProvincia() {
return this.provincia;
}
public void setProvincia(final String provincia) {
this.provincia = provincia;
}
/**
* Il CAP (Codice Avviamento Postale).
*
* @hibernate.property
* length="5"
*/
public String getCap() {
return this.cap;
}
public void setCap(final String cap) {
this.cap = cap;
}
}
Quello
che abbiamo fatto è di dire ad Hibernate quali
sono le entità e le proprietà che devono
essere rese persistenti. Il tag hibernate.class che
abbiamo inserito nella Javadoc della classe Soggetto
dice che vogliamo farne una classe persistente. Il tag
@hibernate.id per getId() segnala che la proprietà
rappresenta la chiave dell'entità ed il parametro
generator-class="native" chiede di usare una
chiave autogenerata dal DBMS. Per tutte le altre proprietà
semplici non facciamo altro che dire con @hibernate.property
che vogliamo una proprietà persistente, in alcuni
casi indicando la lunghezza del campo nel database.
Hibernate, salvo diversamente indicato, determina il
tipo e crea un attributo sul database con lo stesso
nome della proprietà. Le
proprietà più interessanti sono quelle
che riguardano indirizzi e numeri di telefono.
/**
* @hibernate.set
* name="indirizzi"
* @hibernate.collection-key
* column="id"
* @hibernate.collection-composite-element
* class="Indirizzo"
*/
public Set getIndirizzi() {
return this.indirizzi;
}
Con
@hibernate.set specifichiamo che la proprietà
Indirizzi è appunto un insieme, sul quale definiamo
la chiave id. Inoltre, con
@hibernate.collection-composite-element, specifichiamo
anche che la proprietà Indirizzi è una
composizione di oggetti di classe Indirizzo.
Poiché
nel nostro modello la classe Indirizzo non rappresenta
un'entità con esistenza propria bensì
un attributo composto di
Soggetto, omettiamo nella stringa Javadoc della classe
Indirizzo il tag @hibernate.class, ma specifichiamo
comunque le proprietà
persistenti. Per i numeri di telefono abbiamo una specifica
analoga, con la sola differenza che l'attributo numeriTelefono
è multivalore,
ma semplice, per cui possiamo definire l'elemento in
loco con @hibernate.collection-element fornendo il nome
della colonna ed il
tipo. L'ultima notazione d'interesse è quella
per la classe Persona.
/**
* @hibernate.joined-subclass
* @hibernate.joined-subclass-key
* column="id"
*/
public class Persona extends Soggetto {
Poiché
Persona estende Soggetto, ereditandone proprietà
e comportamenti, vogliamo che per la persistenza avvenga
una cosa analoga. Per questo motivo segnaliamo ad Hibernate
che la tabella Persona venga messa in join con la tabella
Soggetto, ottenendo così l'effetto desiderato.
Infatti nel database avremo una situazione di questo
tipo
Soggetto(id,
partitaIva, nome)
Persona(id, dataNascita, codiceFiscale, cognome, sesso)
dove
ogni istanza di Persona corrisponderà un'istanza
di Soggetto con uguale id. Il
nostro lavoro per quanto riguarda lo sviluppo del modello
e della sua persistenza finisce qui. Ci resta solo il
compito di creare uno script di Ant che generi lo schema
del database e i file di mapping per noi. Prima però
vediamo come utilizzare il nostro modello persistente
una volta messo in piedi.
Usare
il modello
La
prima cosa da fare è inizializzare il motore
di Hibernate e aprire le strutture necessarie.
SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Aggiungiamo
una nuova persona:
Persona
p = new Persona();
p.setNome("Rocco");
p.setCognome("Smitelson");
p.setCodiceFiscale("SMTRCC66L31H501P");
p.setSesso("M");
//
data di nascita
Date data = null;
try {
data = new SimpleDateFormat("dd/MM/yyyy").parse("31/10/1966");
} catch (ParseException e) {
System.out.println("Non è stato possibile
fare il parsing della data.");
}
p.setDataNascita(data);
//
recapiti telefonici
p.addTelefono("06123456789");
p.addTelefono("34700112233");
//
indirizzo
Indirizzo indirizzo = new Indirizzo();
indirizzo.setRiferimento("abitazione");
indirizzo.setIndirizzo("via della Paura, 90");
indirizzo.setCap("00100");
indirizzo.setLocalita("Pomezia");
indirizzo.setProvincia("RM");
indirizzo.setComune("Roma");
p.addIndirizzo(indirizzo);
Come
si può vedere lavoriamo sul modello senza consapevolezza
della presenza di un database, utilizzando in tutto
e per tutto Java e le sue strutture. La presenza del
meccanismo di persistenza si avverte solo quando si
decide di salvare l'oggetto appena creato:
session.save(p);
tx.commit();
Tutto
qui. Hibernate si occupa di tradurre il nostro modello
ad oggetti in quello relazionale in modo del tutto trasparente.
Generazione
con Ant
Come
dicevamo, Hibernate ha bisogno di un file di configurazione
che indichi i parametri di accesso al database e gli
schemi di mapping da utilizzare.
<?xml
version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- proprietà di connessione -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/test</property>
<property name="connection.username">utente</property>
<property name="connection.password">password</property>
<!--
SQL dialect -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect
</property>
<property name="hibernate.show_sql">true</property>
<mapping
resource="Soggetto.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Abbiamo
indicato come risorsa di mapping il file Soggetto.hbm.xml.
Questo file ancora non esiste, ma verrà generato
automaticamente dallo script che stiamo per scrivere.
Il
pacchetto XDoclet fornisce un task di Ant per la generazione
dei
file di mapping a partire dai sorgenti, opportunamente
arricchiti come abbiamo visto in precedenza. Allo stesso
modo la libreria di Hibernate contiene un task di Ant
per generare lo schema del database a partire dai file
di mapping. Quello che occorre quindi è uno script
build.xml di Ant configurato con gli opportuni classpath
che lanci i task sui nostri sorgenti.
<?xml
version="1.0"?>
<project name="Persistenza" default="schemaexport"
basedir="..">
<!-- definizione delle location -->
<property name="src.dir" value="src"/>
<property name="src-conf.dir" value="src/conf"/>
<property name="classes.dir" value="build"/>
<property name="setup.dir" value="setup"/>
<!--
definizione delle librerie -->
<property name="hibernate-libs.dir" value="--path
delle librerie di hibernate--"/>
<property name="dbms-libs.dir" value="--path
del driver JDBC per il DB-"/>
<property name="hibernate-xdoclet.dir"
value="--path delle librerie XDoclet"/>
<!-- definizione del classpath -->
<path id="classpath">
<pathelement location="${classes.dir}"/>
</path>
<!--
definizione dei task -->
<taskdef name="schemaexport"
classname="org.hibernate.tool.hbm2ddl.SchemaExportTask"
classpathref="classpath">
<classpath>
<fileset dir="${hibernate-libs.dir}">
<include name="**/*.jar"/>
</fileset>
<fileset dir="${dbms-libs.dir}">
<include name="**/*.jar"/>
</fileset>
</classpath>
</taskdef>
<taskdef
name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask">
<classpath>
<fileset dir="${hibernate-xdoclet.dir}">
<include name="**/*.jar"/>
</fileset>
</classpath>
</taskdef>
<target name="prepare" depends="generate-mappings">
<copy todir="${classes.dir}" >
<fileset dir="${src-conf.dir}" >
<include name="**/*.properties"/>
<include name="**/*.hbm.xml"/>
</fileset>
</copy>
</target>
<target
name="schemaexport" depends="prepare">
<schemaexport config="${src-conf.dir}/hibernate.cfg.xml"
output="${setup.dir}/schema-export.sql">
</schemaexport>
</target>
<target name="generate-mappings">
<hibernatedoclet
destdir="${src-conf.dir}" verbose="true"
force="true" mergedir="">
<fileset dir="${src.dir}">
<include name="**/*.java"/>
</fileset>
<hibernate version="3.0"/>
</hibernatedoclet>
</target>
</project>
E'
sufficiente scrivere lo script di Ant una volta per
tutte, modificando soltanto le locazioni dei sorgenti
e le directory di output caso per caso. Nel nostro esempio
abbiamo i sorgenti in src/, i file di configurazione
(come hibernate.cfg.xml) in src/conf/ e lo script build.xml
in setup/. Lanciando lo script troveremo nella directory
src/conf/ il file di mapping (con lo stesso path della
classe mappata) ed in setup/ lo script SQL per generare
lo schema del database. Se abbiamo fornito dei parametri
di accesso al DBMS con i privilegi di scrittura troveremo
lo schema già presente nel database!
Conclusione
Hibernate
è un motore di persistenza alquanto potente,
che può agire in modi diversi, a seconda del
momento in cui interviene nello sviluppo. Se si progetta
un modello dati completamente ad oggetti Hibernate rende
il meccanismo di persistenza del tutto trasparente,
permettendo allo sviluppatore di interagire con un modello
realmente ad oggetti anziché con tabelle o oggetti-tabella,
realizzando così un'astrazione completa della
base dati.
Riferimenti
Manuale
Hibernate, paragrafo 6.4.1 - http://www.hibernate.org/hib_docs/v3/reference/en/html/mapping.html#mapping-xdoclet
Progetto XDoclet - http://xdoclet.sourceforge.net
|