Capitolo 15. Guida degli strumenti

La cosiddetta "ingegnerizzazione circolare" ("roundtrip engineering") con Hibernate è possibile o utilizzando un insieme di strumenti a linea di comando manutenuti come parte del progetto Hibernate stesso, o sfruttando il supporto ad Hibernate fornito da progetti come XDoclet, Middlegen e AndroMDA.

La distribuzione principale di Hibernate include lo strumento più importante (che può essere usato anche direttamente dall'interno di Hibernate):

Altri strumenti forniti direttamente dal progetto Hibernate vengono rilasciati in un pacchetto separato, detto delle Hibernate Extensions. Il pacchetto include strumenti per i compiti seguenti:

In realtà c'è un altro programma di utilità che sopravvive tra le estensioni di Hibernate: ddl2hbm. Viene considerato deprecato e non viene più manutenuto: Middlegen svolge lo stesso compito facendo un lavoro migliore.

Strumenti di terze parti con supporto per Hibernate sono:

Questi strumenti di terze parti non sono documentati in questo manuale. Fate riferimento al sito di hibernate per informazioni aggiornate al riguardo (una istantanea del sito è disponibile nel pacchetto di distribuzione).

15.1. Generazione dello schema

Il DDL può venire generato dai file di mappaggio tramite una utilità a riga di comando. Un file di comandi ("batch") si trova nella cartella hibernate-x.x.x/bin dell'archivio principale di Hibernate.

Lo schema generato include vincoli di integrità referenziale (chiavi primarie ed esterne) per le entità e le tabelle di collezione. Vengono anche create le tabelle e le sequenze per i generatori di identificatori mappati nei file hbm.

Dovete specificare un dialetto SQL tramite la proprietà hibernate.dialect quando si usa questo strumento.

15.1.1. Personalizzazione dello schema

Molti elementi di mappaggio in Hibernate definiscono un attributo opzionale che si chiama length. Con esso potete impostare la lunghezza di una colonna.

Alcuni tag accettano anche un attributo not-null (che genera un vincolo NOT NULL sulle colonne della tabella) e un attributo unique (per generare vincoli UNIQUE).

Alcuni tag accettano un attributo index per specificare il nome di un indice per la colonna. Un attributo unique-key può essere usato per raggruppare colonne in un vincolo di chiave a singola unità. Attualmente il valore specificato dell'attributo unique-key non viene usato per denominare il vincolo, ma solo per raggruppare le colonne nel file di mappaggio.

Esempi:

<property name="foo" type="string" length="64" not-null="true"/>

<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>

<element column="serial_number" type="long" not-null="true" unique="true"/>

In alternativa, questi elementi accettano anche un elemento figlio <column>, che è particolarmente utile per i tipi multi-colonna:

<property name="foo" type="string">
    <column name="foo" length="64" not-null="true" sql-type="text"/>
</property>

<property name="bar" type="my.customtypes.MultiColumnType"/>
    <column name="fee" not-null="true" index="bar_idx"/>
    <column name="fi" not-null="true" index="bar_idx"/>
    <column name="fo" not-null="true" index="bar_idx"/>
</property>

L'attributo sql-type consente all'utente di sovrascrivere il mappaggio predefinito dal tipo di Hibernate al tipo di dati SQL.

L'attributo check vi consente di specificare un vincolo di controllo.

<property name="foo" type="integer">
    <column name="foo" check="foo > 10"/>
</property>

<class name="Foo" table="foos" check="bar < 100.0">
    ...
    <property name="bar" type="float"/>
</class>

Tabella 15.1. Summary

AttributiValoriInterpretazione
lengthtrue|falselunghezza della colonna
not-nulltrue|falsespecifica che la colonna dovrebbe essere non annullabile
uniquetrue|falsespecifica che la colonna dovrebbe avere un vincolo di unicità
indexnome_indicespecifica il nome di un indice (multicolonna)
unique-keynome_chiave_univocaspecifica il nome di un vincolo di unicità multi-colonna
foreign-keynome_chiave_esterna specifica il nome di un vincolo di chiave esterna generato per un'associazione
sql-typetipo_colonna sovrascrive il tipo di colonna predefinito (solo attributo dell'elemento <column>)
checkespressione SQL crea un vincolo di controllo SQL su una colonna o una tabella

15.1.2. Esecuzione del programma

Lo strumento SchemaExport scrive uno script DDL sull'uscita standard (stdout) e/o esegue le istruzioni DDL.

java -cp classpath_di_hibernate net.sf.hibernate.tool.hbm2ddl.SchemaExport opzioni file_di_mappaggio

Tabella 15.2. Opzioni della linea di comando di SchemaExport

OpzioneDescrizione
--quietnon scrive lo script sull'uscita standard (stdout)
--dropelimina solo le tabelle
--textnon esporta sul database
--output=my_schema.ddlemette lo script ddl su un file
--config=hibernate.cfg.xmllegge la configurazione di Hibernate da un file XML particolare
--properties=hibernate.propertieslegge le proprietà del database da un file
--formatnello script l'SQL generato viene formattato in una maniera "carina"
--delimiter=ximposta un delimitatore di fine linea per lo script

Potete anche annidare SchemaExport nella vostra applicazione:

Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);

15.1.3. Proprietà

Le proprietà del database possono essere specificate

  • come proprietà di sistema con -D<property>

  • in un file hibernate.properties

  • in un file di proprietà con un nome diverso con --properties

Le proprietà richieste sono:

Tabella 15.3. Proprietà di connessione di SchemaExport

Nome proprietàDescrizione
hibernate.connection.driver_classclasse del driver jdbc
hibernate.connection.urlurl jdbc
hibernate.connection.usernamenome utente database
hibernate.connection.passwordparola chiave database
hibernate.dialectdialetto

15.1.4. Utilizzo di Ant

È possibile chiamare lo SchemaExport dal vostro script di Ant:

<target name="schemaexport">
    <taskdef name="schemaexport"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"
        classpathref="class.path"/>
    
    <schemaexport
        properties="hibernate.properties"
        quiet="no"
        text="no"
        drop="no"
        delimiter=";"
        output="schema-export.sql">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaexport>
</target>

15.1.5. Aggiornamenti incrementali dello schema

Lo strumento SchemaUpdate è in grado di aggiornare uno schema esistente con cambiamenti "incrementali". Notate che SchemaUpdate dipende in maniera massiccia dall'API dei metadati JDBC, e per questo non funziona con tutti i driver JDBC.

java -cp classpath_di_hibernate net.sf.hibernate.tool.hbm2ddl.SchemaUpdate opzioni file_di_mappaggio

Tabella 15.4. Opzioni da linea di comando per SchemaUpdate

OpzioneDescrizione
--quietnon scrive lo script su stdout
--properties=hibernate.propertieslegge le proprietà del database da un file

Potete annidare SchemaUpdate nella vostra applicazione:

Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);

15.1.6. Utilizzo di Ant per gli aggiornamenti incrementali dello schema

Potete chiamare SchemaUpdate da uno script di Ant:

<target name="schemaupdate">
    <taskdef name="schemaupdate"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaUpdateTask"
        classpathref="class.path"/>
    
    <schemaupdate
        properties="hibernate.properties"
        quiet="no">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaupdate>
</target>

15.2. Generazione di codice

Il generatore di codice di Hibernate può essere usato per generare l'implementazione della struttura delle classi java da un file di mappaggio di Hibernate. Lo strumento è incluso nel pacchetto delle estensioni di Hibernate (Hibernate Extensions), scaricabile separatamente dal pacchetto principale.

hbm2java interpreta i file di mappaggio e a partire da questi genera classi java complete. In questo modo, usando hbm2java è possibile "solo" fornire i file .hbm e non preoccuparsi della produzione manuale delle classi Java.

java -cp classpath_di_hibernate net.sf.hibernate.tool.hbm2java.CodeGenerator opzioni file_di_mappaggio

Tabella 15.5. Opzioni da linea di comando del generatore di codice

OpzioneDescrizione
--output=cartella_di_outputcartella radice per il codice generato
--config=file_di_configurazionefile opzionale per configurare hbm2java

15.2.1. Il file di configurazione (opzionale)

Il file di configurazione fornisce una maniera per specificare dei "produttori" multipli per il codice sorgente e per dichiarare attributi <meta> che sono "globali" per visibilità. Leggete di più al riguardo nella sezione sull'attributo <meta>.

<codegen>
    <meta attribute="implements">codegen.test.IAuditable</meta>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate
        package="autofinders.only"
        suffix="Finder"
        renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

Questo file di configurazione dichiara un attributo meta globale "implements" e specifica due produttori (renderers), quello predefinito (BasicRenderer) e un produttore che genera dei "Finder" (vedete anche in "generazione basica dei finder" più sotto).

Il secondo produttore viene fornito con attributi "package" e "suffix".

L'attributo "package" specifica che i file di codice sorgente generati da questo renderer dovrebbero essere posti in questo package invece che in quello specificato nei file .hbm.

L'attributo "suffix" specifica il suffisso per i file generati. Nel caso dell'esempio, un file chiamato Foo.java diventerebbe invece FooFinder.java.

15.2.2. L'attributo meta

L'etichetta <meta> è una maniera semplice di annotare il file hbm.xml, e dare agli strumenti un posto naturale per memorizzare o leggere informazioni che non siano direttamente correlate con il nucleo di Hibernate.

Potete usare l'etichetta <meta> per indicare ad hbm2java di generare solo metodi "setter" protetti, fare in modo tale che le classi implementino sempre un certo insieme di interfacce, fare in modo tale che estendano una certa classe di base, o altro.

L'esempio seguente:

<class name="Person">
    <meta attribute="class-description">
        Javadoc per la classe Person
        @author Frodo
    </meta>
    <meta attribute="implements">IAuditable</meta>
    <id name="id" type="long">
        <meta attribute="scope-set">protected</meta>
        <generator class="increment"/>
    </id>
    <property name="name" type="string">
        <meta attribute="field-description">Il nome della persona</meta>
    </property>
</class>

produrrà qualcosa come ciò che segue (il codice è stato accorciato per renderlo più comprensibile). Notate il commento Javadoc e il metodo set protetto:

// package di default

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/** 
 *         Javadoc per la classe Person
 *         @author Frodo
 *     
 */
public class Person implements Serializable, IAuditable {

    /** identifier field */
    public Long id;

    /** nullable persistent field */
    public String name;

    /** full constructor */
    public Person(java.lang.String name) {
        this.name = name;
    }

    /** default constructor */
    public Person() {
    }

    public java.lang.Long getId() {
        return this.id;
    }

    protected void setId(java.lang.Long id) {
        this.id = id;
    }

    /** 
     * Il nome della persona
     */
    public java.lang.String getName() {
        return this.name;
    }

    public void setName(java.lang.String name) {
        this.name = name;
    }

}

Tabella 15.6. Meta tag supportati

AttributoDescrizione
class-descriptioninserito nel javadoc per le classi
field-descriptioninserito nel javadoc per i campi/proprietà
interfaceSe è vero viene generata un'interfaccia invece di una classe.
implementsl'interfaccia che la classe deve implementare
extendsclasse che dovrebbe essere estesa da questa classe (ignorata per le sottoclassi)
generated-classsovrascrive il nome della vera classe generata
scope-classvisibilità per la classe
scope-setvisibilità per un metodo setter
scope-getvisibilità per un metodo getter
scope-fieldvisibilità per il campo vero e proprio
use-in-tostringinclude la proprietà nel toString()
implement-equalsinclude un metodo equals() e un hashCode() in questa classe.
use-in-equalsinclude la proprietà nei metodi equals() e hashCode().
boundaggiunge il supporto di un propertyChangeListener per la proprietà
constrainedcome bound + il supporto di un vetoChangeListener per una proprietà
gen-propertyla proprietà non verrà generata se è falsa (usare con cautela)
property-typeSovrascrive il tipo di default della proprietà. Da usare con l'etichetta "any" per specificare il tipo concreto invece di avere solo Object.
class-codeCodice extra che verrà inserito alla fine della classe
extra-importClausola di importazione extra che verrà inserita alla fine di tutte le altre
finder-methodvedere "generatore elementare di metodi individuatori" più sotto
session-methodvedere "generatore elementare di metodi individuatori" più sotto

All'interno di un file hbm.xml, gli attributi dichiarati tramite l'elemento <meta> come comportamento predefinito vengono "ereditati".

Cosa significa? Significa che se ad esempio volete fare sì che tutte le vostre classi implementino l'interfaccia IAuditable dovete solo aggiungere un <meta attribute="implements">IAuditable</meta> all'inizio del file hbm.xml, proprio dopo <hibernate-mapping>. Ora tutte le classi definite in quel file hbm.xml implementeranno IAuditable! (Eccetto se una classe ha anche un attributo meta "implements", perché le etichette meta specificate localmente sovrascrivono/rimpiazzano sempre quelle ereditate).

Nota: questo si applica a tutti i <meta>-tag. Così può anche essere usato ad esempio per specificare che tutti i campi dovrebbero essere dichiarati protetti, invece che privati come è il comportamento predefinito. Questo si imposta aggiungendo <meta attribute="scope-field">protected</meta> ad esempio proprio sotto l'elemento <class>, e tutti i campi della classe saranno generati come protetti.

Per evitare che un <meta>-tag venga ereditato potete semplicemente specificare inherit="false" per l'attributo, ad esempio <meta attribute="scope-class" inherit="false">public abstract</meta> restringerà la visibilità di classe alla classe corrente, e non alle sottoclassi.

15.2.3. Generatore elementare di metodi individuatori ("finder")

Ora è possibile fare in modo tale che hbm2java generi dei metodi individuatori elementari per le proprietà di Hibernate. Questo richiede che due cose vengano impostate nel file hbm.xml.

La prima è l'indicazione di quale sia il campo per cui si vogliono generare gli individuatori. Si indica con un blocco meta all'interno di un elemento "property", come in:

<property name="name" column="name" type="string">
     <meta attribute="finder-method">findByName</meta>
</property>

Il nome del metodo individuatore sarà il testo racchiuso nelle etichette meta.

Il secondo è la creazione di un file di configurazione per hbm2java nella forma:

<codegen>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate suffix="Finder" renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

A questo punto si deve usare il parametro per hbm2java --config=xxx.xml laddove xxx.xml è il file di configurazione che è appena stato creato.

Un parametro opzionale è l'etichetta meta al livello di classe nel formato:

<meta attribute="session-method">
    com.whatever.SessionTable.getSessionTable().getSession();
</meta>

Che sarebbe il modo in cui si reperiscono le sessioni se usate il pattern Thread Local Session (documentato nell'area "Design Patterns" del sito web di Hibernate).

15.2.4. Generatore basato su Velocity

Ora è possibile usare Velocity come strumento alternativo di resa/generazione. Il seguente file config.xml mostra come configurare hbm2java per usare il generatore basato su velocity.

    <codegen>
     <generate renderer="net.sf.hibernate.tool.hbm2java.VelocityRenderer">
      <param name="template">pojo.vm</param>
     </generate>
    </codegen>

Il parametro template è un percorso di risorsa che punta al file delle macro velocity macro che volete usare. Il file deve essere raggiungibile sul classpath di hbm2java: per questo ricordate di aggiungere al vostro task di ant o script di shell la directory in cui si trova pojo.vm (la posizione predefinita è ./tools/src/velocity)

Ricordatevi che la versione attuale di pojo.vm genera solo le parti più elementari dei bean java. Non è tanto completa e ricca di funzionalità quanto il generatore nativo di Hibernate - in particolare non sono supportati la maggior parte dei tag meta.

15.3. Generazione dei file di mappaggio

È possibile generare uno scheletro di file di mappaggio a partire da classi persistenti compilate usando una utilità a linea di comando chiamata MapGenerator, parte del pacchetto delle estensioni di Hibernate (Hibernate Extensions).

Il generatore di mappaggio fornisce un meccanismo per produrre mappaggi dalle classi compilate. Usa il meccanismo della "reflection" java per trovare le proprietà e usa dei metodi euristici per indovinare un mappaggio appropriato per il tipo di proprietà. Il mappaggio generato è inteso solo come un punto di partenza: non c'è modo di produrre un mappaggio completo senza informazioni extra fornite dall'utente. In ogni modo, questo strumento libera da una parte del lavoro ripetitivo e bruto coinvolto nella produzione di un mappaggio.

Le classi vengono aggiunte al mappaggio una alla volta. Lo strumento rigetterà le classi che a suo giudizio non siano persistibili tramite Hibernate.

Per essere persistibile tramite Hibernate una classe

  • non deve essere un tipo primitivo

  • non dev'essere un array

  • non deve essere un'interfaccia

  • non deve essere una classe annidata

  • deve avere un costruttore di default (senza argomenti).

Notate che le interfacce e le classi annidate in realtà sono persistibili da Hibernate, ma questo non è solitamente ciò che l'utente vuole.

MapGenerator risalirà la catena delle superclassi di tutte le classi aggiunte tentando di aggiungere quante più superclassi possibile (persistibili da Hibernate) alla stessa tabella di database. La ricerca si ferma non appena viene trovata una proprietà che ha un nome che appare in una lista di nomi candidati come UID.

La lista predefinita di nomi di proprietà candidati come UID è: uid, UID, id, ID, key, KEY, pk, PK.

Le proprietà vengono reperite quando ci sono due metodi nella classe, un "setter" (impostatore) e un "getter" (recuperatore), laddove il tipo del singolo argomento dell'impostatore è lo stesso del tipo di ritorno del recuperatore (che non deve avere argomenti), mentre l'impostatore restituisce void. Inoltre, il nome dell'impostatore deve cominciare con la stringa set e deve essere vero o che il nome del recuperatore comincia con get o che comincia con is e il tipo della proprietà è boolean. In entrambi i casi, il resto dei nomi deve conocordare. Questa porzione corrispondente è il nome della proprietà, eccettuato il fatto che il carattere iniziale del nome della proprietà è reso minuscolo se la seconda lettera è minuscola.

Le regole per determinare il tipo (sul database) di ogni proprietà sono:

  1. Se il tipo java è Hibernate.basic(), la proprietà è una colonna di quel tipo.

  2. Per i tipi personalizzati hibernate.type.Type e PersistentEnum viene usata una colonna semplice, nello stesso modo.

  3. Se il tipo della proprietà è un array, viene usato un array di Hibernate, e MapGenerator tenta di riflettere (ovvero agire via "reflection") sul tipo di elemento dell'array.

  4. Se la proprietà ha tipo java.util.List, java.util.Map, o java.util.Set, vengono usati i corrispondenti tipi di Hibernate, ma MapGenerator non può procedere oltre nel lavorare sull'interno di questi tipi.

  5. Se il tipo della proprietà è qualsiasi altra classe, MapGenerator rimanda la decisione sulla rappresentazione sul database finché tutte le classi non sono state processate. A questo punto, se la classe era stata reperita tramite la ricerca per superclassi descritta sopra, allora la proprietà è una associazione many-to-one. Se la classe ha delle proprietà, allora è un component. In caso contrario è serializzabile, o non persistibile.

15.3.1. Esecuzione dello strumento

Lo strumento scrive mappaggi XML sull'uscita standard e/o su un file.

Quando invocate lo strumento, dovete mettere sul classpath le vostre classi compilate.

java -cp classpath_di_hibernate_e_delle_vostre_classi net.sf.hibernate.tool.class2hbm.MapGenerator opzioni e nomi delle classi

Ci sono due modi di funzionamento: a linea di comando o interattivo.

La modalità interattiva viene selezionata fornendo sulla linea di comando il parametro --interact. Questa modalità fornisce una console con un "prompt" (cursore di inserimento comandi). Usandola potete settare il nome della proprietà UID per ogni classe usando il comando uid=XXX in cui XXX è il nome della proprietà UID. Altre alternative di comandi sono semplicemente un nome di classe completamente qualificato (cioè con la parte relativa ai package, come in java.lang.String), o il comando "done" che emette l'XML è termina.

In modalità a linea di comando i parametri sono le opzioni che seguono, inframmezzate dai nomi completamente qualificati delle classi che vanno processate. La maggior parte delle opzioni sono intese come utilizzabili più volte; ogni uso coinvolge le classi che vengono aggiunte conseguentemente.

Tabella 15.7. Opzioni da linea di comando del MapGenerator

OpzioneDescrizione
--quietnon scrive il mappaggio O-R sullo standard output
--setUID=uidimposta la lista di UID candidati all'uid singolo
--addUID=uidaggiunge uid in cima alla lista di UID candidati
--select=modalitàusa la modalità di selezione modalità(e.g., distinct o all) per le classi aggiunte in seguito
--depth=<piccolo-valore-intero>limita la profondità della ricorsione dei dati dei componenti per le classi aggiunte in seguito
--output=mio_file.xmlscrive il mappaggio OR su un file
nome.completo.di.Classeaggiunge la classe al mappaggio
--abstract=nome.completo.di.Classevedi sotto

Il parametro "abstract" istruisce lo strumento di generazione del mappaggio in modo tale da ignorare superclassi specifiche in modo che classi con ereditarietà comune non vengano mappate su una sola grande tabella. Ad esempio, considerate queste gerarchie di classe:

Animale-->Mammifero-->Umano

Animale-->Mammifero-->Marsupiale-->Canguro

Se il parametro --abstract non viene usato, tutte le classi verranno mappate come sottoclassi di Animale, il che risulterà in una grande tabella che contiene tutte le proprietà di tutte le classi più una colonna discriminatore che indicherà quale sottoclasse è realmente memorizzata in una riga. Se Mammifero è marcata come abstract, Umano e Marsupiale verranno mappate in dichiarazioni <class> separate e memorizzate in tabelle separate. Canguro sarà ancora una sottoclasse di Marsupiale a meno che anche Marsupiale sia marchiata come abstract.