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):
Generazione di uno schema DDL da un file di mappaggio (cioè SchemaExport, hbm2ddl)
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:
Generazione di sorgenti Java da un file di mappaggio (cioè CodeGenerator, hbm2java)
generazione di file di mappaggio da classi Java compilate o da sorgenti Java con indicazioni di contrassegno ("markup") di XDoclet markup (ovvero MapGenerator, class2hbm)
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:
Middlegen (generazione di file di mappaggio da uno schema di database esistente)
AndroMDA (MDA (Model-Driven Architecture o architettura guidata dal modello) è un approccio alla generazione di codice per classi persistenti a partire da diagrammi UML e dalla loro rappresentazione XML/XMI
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).
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.
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
Attributi | Valori | Interpretazione |
---|---|---|
length | true|false | lunghezza della colonna |
not-null | true|false | specifica che la colonna dovrebbe essere non annullabile |
unique | true|false | specifica che la colonna dovrebbe avere un vincolo di unicità |
index | nome_indice | specifica il nome di un indice (multicolonna) |
unique-key | nome_chiave_univoca | specifica il nome di un vincolo di unicità multi-colonna |
foreign-key | nome_chiave_esterna | specifica il nome di un vincolo di chiave esterna generato per un'associazione |
sql-type | tipo_colonna | sovrascrive il tipo di colonna predefinito (solo attributo dell'elemento <column>) |
check | espressione SQL | crea un vincolo di controllo SQL su una colonna o una tabella |
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
Opzione | Descrizione |
---|---|
--quiet | non scrive lo script sull'uscita standard (stdout) |
--drop | elimina solo le tabelle |
--text | non esporta sul database |
--output=my_schema.ddl | emette lo script ddl su un file |
--config=hibernate.cfg.xml | legge la configurazione di Hibernate da un file XML particolare |
--properties=hibernate.properties | legge le proprietà del database da un file |
--format | nello script l'SQL generato viene formattato in una maniera "carina" |
--delimiter=x | imposta un delimitatore di fine linea per lo script |
Potete anche annidare SchemaExport nella vostra applicazione:
Configuration cfg = ....; new SchemaExport(cfg).create(false, true);
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:
È 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>
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
Opzione | Descrizione |
---|---|
--quiet | non scrive lo script su stdout |
--properties=hibernate.properties | legge le proprietà del database da un file |
Potete annidare SchemaUpdate nella vostra applicazione:
Configuration cfg = ....; new SchemaUpdate(cfg).execute(false);
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>
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
Opzione | Descrizione |
---|---|
--output=cartella_di_output | cartella radice per il codice generato |
--config=file_di_configurazione | file opzionale per configurare hbm2java |
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.
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
Attributo | Descrizione |
---|---|
class-description | inserito nel javadoc per le classi |
field-description | inserito nel javadoc per i campi/proprietà |
interface | Se è vero viene generata un'interfaccia invece di una classe. |
implements | l'interfaccia che la classe deve implementare |
extends | classe che dovrebbe essere estesa da questa classe (ignorata per le sottoclassi) |
generated-class | sovrascrive il nome della vera classe generata |
scope-class | visibilità per la classe |
scope-set | visibilità per un metodo setter |
scope-get | visibilità per un metodo getter |
scope-field | visibilità per il campo vero e proprio |
use-in-tostring | include la proprietà nel toString() |
implement-equals | include un metodo equals() e un hashCode() in questa classe. |
use-in-equals | include la proprietà nei metodi equals() e hashCode(). |
bound | aggiunge il supporto di un propertyChangeListener per la proprietà |
constrained | come bound + il supporto di un vetoChangeListener per una proprietà |
gen-property | la proprietà non verrà generata se è falsa (usare con cautela) |
property-type | Sovrascrive il tipo di default della proprietà. Da usare con l'etichetta "any" per specificare il tipo concreto invece di avere solo Object. |
class-code | Codice extra che verrà inserito alla fine della classe |
extra-import | Clausola di importazione extra che verrà inserita alla fine di tutte le altre |
finder-method | vedere "generatore elementare di metodi individuatori" più sotto |
session-method | vedere "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.
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).
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.
È 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:
Se il tipo java è Hibernate.basic(), la proprietà è una colonna di quel tipo.
Per i tipi personalizzati hibernate.type.Type e PersistentEnum viene usata una colonna semplice, nello stesso modo.
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.
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.
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.
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
Opzione | Descrizione |
---|---|
--quiet | non scrive il mappaggio O-R sullo standard output |
--setUID=uid | imposta la lista di UID candidati all'uid singolo |
--addUID=uid | aggiunge 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.xml | scrive il mappaggio OR su un file |
nome.completo.di.Classe | aggiunge la classe al mappaggio |
--abstract=nome.completo.di.Classe | vedi 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.