I mappaggi oggetto/relazione vengono definiti in un documento XML. Il documento di mappaggio è progettato per essere leggibile e modificabile a mano. Il linguaggio di mappaggio è java-centrico, nel senso che i mappaggi sono costruiti intorno alle dichiarazioni delle classi persistenti, non sulle dichiarazioni delle tabelle.
Notate che anche se molti utenti di Hibernate scelgono di definire i mappaggi XML a mano, esistono un certo numero di strumenti per generare il documento di mappaggio, tra cui XDoclet, Middlegen e AndroMDA.
Ora cominciamo con un mappaggio di esempio:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="CATS" discriminator-value="C"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <discriminator column="subclass" type="character"/> <property name="birthdate" type="date"/> <property name="color" not-null="true"/> <property name="sex" not-null="true" update="false"/> <property name="weight"/> <many-to-one name="mate" column="mate_id"/> <set name="kittens"> <key column="mother_id"/> <one-to-many class="Cat"/> </set> <subclass name="DomesticCat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </class> <class name="Dog"> <!-- qui potrebbe stare il mappaggio per Dog --> </class> </hibernate-mapping>
Ora discuteremo il contenuto del documento di mappaggio. Descriveremo solo gli elementi e gli attributi del documento che Hibernate usa in fase di esecuzione. Il documento di mappaggio contiene anche alcuni elementi ed attributi opzionali che hanno effetto sugli schemi del database exportati dallo strumento di generazione dello schema (schemaexport). (Ad esempio l'attributo not-null.)
Tutti i mappaggi XML dovrebbero dichiarare il che abbiamo mostrato nell'esempio. L'effettivo DTD può essere trovato all'URL indicato, nella directory hibernate-x.x.x/src/net/sf/hibernate del pacchetto di Hibernate o nel file hibernate.jar. Hibernate cercherà sempre per prima cosa il DTD sul classpath.
Questo elemento ha tre attributi opzionali. L'attributo schema specifica che le tabelle a cui si fa riferimento nel mappaggio appartengono allo schema indicato. Se viene usato, i nomi delle tabelle saranno completati dal nome dello schema inidicato. Se manca, i nomi delle tabelle non saranno ulteriormente caratterizzati. L'attributo default-cascade specifica quale stile di cascata dovrebbe essere assunto per le proprietà e le collezioni che non specificano un attributo cascade attribute. L'attributo auto-import ci consente di utilizzare nomi di classe non qualificati nel linguaggio di interrogazione come comportamento predefinito.
<hibernate-mapping schema="schemaName" (1) default-cascade="none|save-update" (2) auto-import="true|false" (3) package="package.name" (4) />
(1) | schema (opzionale): Il nome di uno schema del database. |
(2) | default-cascade (opzionale - il default è none): Uno stile di cascata predefinito. |
(3) | auto-import (opzionale - il default è true): Specifica se possiamo usare nomi di classe non qualificati (le classi devono essere di questo mappaggio) nel linguaggio di interrogazione. |
(4) | package (opzionale): Specifica un prefisso di package da assumere per i nomi di classi non qualificati nel documento di mappaggio. |
Se avete due classi persistenti con lo stesso nome (non qualificato), dovreste impostare auto-import="false". Hibernate lancerà un'eccezione se tentate di assegnare due classi diversse allo stesso nome "importato".
L'elemento class si usa per dichiarare una classe persistente:
<class name="ClassName" (1) table="tableName" (2) discriminator-value="discriminator_value" (3) mutable="true|false" (4) schema="owner" (5) proxy="ProxyInterface" (6) dynamic-update="true|false" (7) dynamic-insert="true|false" (8) select-before-update="true|false" (9) polymorphism="implicit|explicit" (10) where="arbitrary sql where condition" (11) persister="PersisterClass" (12) batch-size="N" (13) optimistic-lock="none|version|dirty|all" (14) lazy="true|false" (15) />
(1) | name: il nome di classe java completamente qualificato della classe persistente (o l'interfaccia). |
(2) | table: il nome della sua tabella di database. |
(3) | discriminator-value (opzionale - il default è il nome della classe): un valore che distingue sottoclassi individuali, usato per il comportamento polimorfico. I valori accettabili includono null e not null. |
(4) | mutable (opzionale, il default è true): specifica che le istanze della classe (non) sono mutabili. |
(5) | schema (opzionale): sovrascrive il nome dello schema specificato dall'elemento radice <hibernate-mapping>. |
(6) | proxy (opzionale): specifica una interfaccia da usare per i mediatori (proxy) ad inizializzazione ritardata. Potete specificare il nome della classe stessa. |
(7) | dynamic-update (opzionale, il default è false): specifica che una UPDATE SQL dovrebbe venire generata in fase di esecuzione e contenere solo i nomi delle colonne di cui sono cambiati i valori. |
(8) | dynamic-insert (opzionale, il default è false): specifica che le INSERT SQL dovrebbero venire generate in fase di esecuzione e contenere solo i nomi delle colonne i cui valori sono non nulli. |
(9) | select-before-update (opzionale, il default è false): specifica che Hibernate non dovrebbe mai eseguire una UPDATE a meno che non sia certo che un oggetto non sia davvero stato modificato. In certi casi (in realtà solo quando un oggetto transiente sia stato associato ad una nuova sessione usando update()), questo significa che Hibernate effettuerà una istruzione SQL SELECT in più per determinare se UPDATE sia realmente richiesto. |
(10) | polymorphism (opzionale, il default è implicit): determina se deve essere usato un polimorfismo di interrogazione implicito o esplicito. |
(11) | where (opzionale) specifica una condizione WHERE dell'SQL arbitraria da usare quando si recuperano oggetti di questa classe |
(12) | persister (opzionale): specifica un ClassPersister personalizzato. |
(13) | batch-size (opzionale, il default è 1) specifica una "dimensione di blocco" (batch) per il caricamento di istanze di questa classe per identificatore. |
(14) | optimistic-lock (opzionale, il default è version): Determina la strategia di locking ottimistico. |
(15) | lazy (opzionale): impostare lazy="true" è una scorciatoia equivalente a specificare il nome stesso della classe come interfaccia proxy. |
È perfettamente accettabile che il nome della classe persistente sia un'interfaccia. In questo caso si dichiarano le classi di implementazione di quell'interfaccia utilizzando l'elemento <subclass>. Potete persistere anche classi interne static. In questo caso dovete specificare il nome della classe usando la forma standard , cioè eg.Foo$Bar.
Le classi immutabili, mutable="false" non possono essere aggiornate o cancellate dall'applicazione. Questo consente ad Hibernate di effettuare alcune ottimizzazioni di performance minori.
L'attributo proxy opzionale consente l'inizializzazione ritardata delle istanze persistenti della classe. Hibernate inizialmente restituirà dei mediatori (proxy) CGLIB che implementano l'interfaccia indicata. Il vero oggetto persistente sarà caricato quando si invocherà un metodo del mediatore. Leggete più oltre il paragrafo "Mediatori per l'inizializzazione ritardata".
Il polimorfismo implicito significa che interrogazioni che indicheranno i nomi di una qualsiasi superclasse o interfaccia implementata da una classe potranno restituire istanze di quella classe stessa, e che una query che indichi il nome della classe stessa potrà restituire anche istanze di una qualsiasi sottoclasse. Il polimorfismo Explicit significa che le istanze di una classe verranno restituite esclusivamente da interrogazioni che indichino esplicitamente il nome di quella classe, e che interrogazioni che indichino il nome di quella classe restituiranno esclusivamente nomi di sottoclassi mappati all'interno di questa dichiarazione <class> come <subclass> o <joined-subclass>. Per la maggior parte degli scopi, l'impostazione predefinita, ovvero polymorphism="implicit", è appropriata. Il polimorfismo esplicito è utile quando due classi diverse vengano mappate sulla stessa tabella (questo consente di avere una classe "leggera" che contiene un sottoinsieme delle colonne della tabella).
L'attributo persister vi consente di personalizzare la strategia di persistenza utilizzata per la classe. potete, ad esempio, specificare la vostra sottoclasse di net.sf.hibernate.persister.EntityPersister o potete addiritura fornire una implementazione completamente diversa dell'interfaccia net.sf.hibernate.persister.ClassPersister che implementi la persistenza via, ad esempio, chiamate a procedure memorizzate (stored procedure), serializzazione su file piatti o LDAP. Andate a vedere il codice di net.sf.hibernate.test.CustomPersister per un esempio semplice (di "persistenza" su una Hashtable).
Notate che le impostazioni dynamic-update e dynamic-insert non vengono ereditate dalle sottoclassi, e quindi potrebbero venire anche specificate sugli elementi <subclass> o <joined-subclass>. Queste impostazioni possono aumentare le performance, in certi casi, ma potrebbero in realtà diminuire le performance in altri. Usatele con giudizio.
L'uso di select-before-update di solito diminuirà le performance. È molto utile però per evitare che dei trigger sul database associati all'update vengano chiamati inutilmente.
Se abilitate dynamic-update, avrete una scelta fra strategie di locking ottimistico:
version controlla le colonne di versione/marca di tempo
all controlla tutte le colonne
dirty controlla le colonne cambiate
none non usa il locking ottimistico
Raccomandiamo molto che usiate le colonne di versione/marca di tempo per il locking ottimistico con Hibernate. Si tratta della strategia ottimale rispetto alle performance, ed è la sola strategia che gestisca correttamente le modifiche fatte al di fuori della sessione, (ad esempio quando venga usato Session.update()). Ricordatevi che una proprietà di versione o marca di tempo non dovrebbe mai essere nulla, indipendentemente da quale sia la strategia unsaved-value, o un'istanza verrà individuata come transiente.
Le classi mappate devono dichiarare la colonna di chiave primaria della tabella sul database. La maggior parte delle classi avrà anche una proprietà nello stile dei javabean (cioè con metodi "getter" e "setter") che manterrà l'identificatore unico di un'istanza. L'elemento <id> definisce il mappaggio da quella proprietà alla colonna di chiave primaria.
<id name="propertyName" (1) type="typename" (2) column="column_name" (3) unsaved-value="any|none|null|id_value" (4) access="field|property|ClassName"> (5) <generator class="generatorClass"/> </id>
(1) | name (opzionale): il nome della proprietà identificatore. |
(2) | type (opzionale): un nome che indica il tipo di Hibernate. |
(3) | column (opzionale - il default è il nome della proprietà): il nome della colonna di chiave primaria. |
(4) | unsaved-value (opzionale - il default è null): un valore di proprietà di identificazione che indichi che un'istanza è appena stata istanziata (è "unsaved"), distinguendola da istanze transienti che siano state salvate o caricate in una sessione precedente. |
(5) | access (opzionale - il default è property): la strategia che Hibernate dovrebbe usare per accedere al valore della proprietà. |
Se l'attributo name manca, si assume che la classe non abbia proprietà identificatore.
L'attributo unsaved-value è importante! Se la proprietà identificatore della vostra classe non ha null come valore iniziale, allora dovreste specificare il valore.
C'è una dichiarazione alternativa, <composite-id>, per consentire accesso a dati preesistenti con chiavi composite. Scoraggiamo fortemente il suo uso per qualsiasi altro motivo.
L'elemento figlio obbligatorio <generator> indica una classe Java utilizzata per generare identificatori unici per istanze di questa classe persistente. Se l'istanza del generatore richiedesse di essere configurata o inizializzata con dei parametri, questi possono essere passati usando l'elemento <param>.
<id name="id" type="long" column="uid" unsaved-value="0"> <generator class="net.sf.hibernate.id.TableHiLoGenerator"> <param name="table">uid_table</param> <param name="column">next_hi_value_column</param> </generator> </id>
Tutti i generatori implementano l'interfaccia net.sf.hibernate.id.IdentifierGenerator. È un'interfaccia molto semplice; alcune applicazioni potrebbero scegliere di fornire le loro implementazioni specializzate. In ogni caso, Hibernate fornisce un certo numero di implementazioni preinstallate. Ci sono anche dei nomi abbreviati per i generatori preinstallati:
genera identificatori di tipo long, short o int che sono unici solo quando nessun altro processo inserisce dati nella stessa tabella. Da non usare in un cluster.
supporta le colonne "identity" in DB2, MySQL, MS SQL Server, Sybase e HypersonicSQL. L'identificatore restituito è di tipo long, short o int.
usa una "sequence" in DB2, PostgreSQL, Oracle, SAP DB, McKoi o un generatore in Interbase. L'identificatore restituito è di tipo long, short o int.
usa un algoritmo hi/lo per generare efficientemente identificatori di tipo long, short o int, date una tabella e una colonna (per default hibernate_unique_key e next rispettivamente) come sorgente di valori "hi". L'algoritmo hi/lo genera identificatori che sono unici solo per un particolare database. Non usate questo generatore con connessioni iscritte con JTA o con una connessione fornita da voi stessi.
usa un algoritmo hi/lo per generare efficientemente identificatori di tipo long, short o int, dato il nome di una sequenza sul database.
usa un algoritmo UUID a 128-bit per generare identificatori di tipo stringa, unici all'interno di una rete (viene usato l'indirizzo IP). L'UUID è codificato come una stringa di 32 caratteri esadecimali.
usa lo stesso algoritmo UUID. L'UUID è codificato come una stringa di lunghezza 16 che consiste di (qualsiasi) carattere ASCII. Non usare con PostgreSQL.
usa identity, sequence o hilo a seconda delle capacità del database sottostante.
lascia all'applicazione il compito di assegnare un identificatore all'oggetto prima che venga chiamato save().
usa l'identificatore di un altro oggetto associato. Solitamente è usato insieme a una associazione di chiave primaria <one-to-one>.
I generatori hilo e seqhilo forniscono due implementazioni alternative dell'algoritmo hi/lo, un approccio importante per la generazione di identificatori. La prima implementazione richiede una tabella "speciale" del database per mantenere il prossimo valore "hi" disponibile. La seconda usa una "sequence" nello stile di Oracle (dove sia supportata).
<id name="id" type="long" column="cat_id"> <generator class="hilo"> <param name="table">hi_value</param> <param name="column">next_value</param> <param name="max_lo">100</param> </generator> </id>
<id name="id" type="long" column="cat_id"> <generator class="seqhilo"> <param name="sequence">hi_value</param> <param name="max_lo">100</param> </generator> </id>
Sfortunatamente non è possibile usare hilo quando fornite le vostre Connection a Hibernate, o quando Hibernate sta usando il datasource di un application server per ottenere connessioni iscritte con il JTA. Hibernate deve essere in grado di raccogliere il valore "hi" in una nuova transazione. Un approccio standard in un ambiente EJB è di implementare l'algoritmo hi/lo usando un session bean senza stato.
Gli UUIDs contengono: indirizzo IP, tempo di partenza della JVM (accurato al quarto di secondo), il tempo di sistema e il valore di un contatore (unico all'interno della JVM). Non è possibile ottenere un indirizzo MAC o un indirizzo di memoria da del codice java, quindi questo è il massimo che possiamo fare senza usare JNI.
Non tentate di usare uuid.string in PostgreSQL.
Per i database che supportano le colonne "identity" (DB2, MySQL, Sybase, MS SQL), potete usare la generazione di chiave identity. Per i database che supportano le sequenze (DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB) potete usare la generazione di chiave nello stile sequence. Entrambe queste strategie richiedono due istruzioni SQL per inserire un nuovo oggetto.
<id name="id" type="long" column="uid"> <generator class="sequence"> <param name="sequence">uid_sequence</param> </generator> </id>
<id name="id" type="long" column="uid" unsaved-value="0"> <generator class="identity"/> </id>
Per lo sviluppo cross-piattaforma, la strategia native sceglierà dalle strategieidentity, sequence e hilo, in maniera dipendente dalle capacità del database sottostante.
Se volete che sia l'applicazione ad assegnare gli identificatori (rispetto ad una situazione in cui è Hibernate che li genera), potete usare il generatore assigned. Questo generatore speciale userà il valore di identificatore già assegnato alla proprietà di identificazione dell'oeggetto. State molto attenti quando usate questa funzionalità a non assegnare chiavi con significato di business (quasi sempre una terribile decisione di design).
A causa della sua natura implicita, le entità che usano questo generatore non possono essere salvate usando il metodo saveOrUpdate() della Session. Invece dovete specificare esplicitamente ad Hibernate se l'oggetto dovrebbe essere salvato o aggiornato chiamando o il metodo save() o il metodo update() della Session.
<composite-id name="propertyName" class="ClassName" unsaved-value="any|none" access="field|property|ClassName"> <key-property name="propertyName" type="typename" column="column_name"/> <key-many-to-one name="propertyName class="ClassName" column="column_name"/> ...... </composite-id>
Per una tabella con una chiave composita, potete mappare proprietà multiple della classe come proprietà identificatore. L'elemento <composite-id> accetta mappaggi di proprietà <key-property> e <key-many-to-one> come elementi figli.
<composite-id> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id>
La vostra classe persistente deve sovrascrivere equals() e hashCode() per implementare l'uguaglianza degli identificatori composti. Deve anche implementare Serializable.
Sfortunatamente, questo approccio agli identificatori compositi significa che un oggetto persistente è il suo proprio identificatore. Non c'è un "handle" conveniente al di là dell'oggetto stesso. Dovete istanziare un oggetto della classe persistente e popolare le sue proprietà di identificazione, prima che possiate caricare (load()) lo stato persistente associato ad una classe composita. Descriveremo un approccio molto più conveniente in cui l'identificatore composito sarà implementato come una classe separata nel paragrafo Sezione 7.4, “Componenti come identificatori composti”. Gli attributi descritti sotto si applicano solo a questo approccio alternativo:
name (opzionale): una proprietà di un tipo di componente che mantiene l'identificatore composito (vedete la prossima sezione).
class (opzionale - il default è il tipo di proprietà ricavato via "reflection"): la classe di componente usata come identificatore composito (vedere la prossima sezione).
unsaved-value (opzionale - il default è none): indica che le istanze transienti dovrebbero essere considerate appena istanziate, se impostato ad any.
L'elemento <discriminator> è richiesto per la persistenza polimorfica quando si usa la strategia di mappaggio "tabella per gerarchia di classi" e dichiara una colonna discriminatore della tabella. La colonna discriminatore contiene valori di indicazione che informano lo strato persistente riguardo alla particolare sottoclasse da istanziare per una riga. Si può usare solo un insieme ristretto di tipi: string, character, integer, byte, short, boolean, yes_no, true_false.
<discriminator column="discriminator_column" (1) type="discriminator_type" (2) force="true|false" (3) />
(1) | column (opzionale - il default è class) il nome della colonna discriminatore. |
(2) | type (opzionale - il default è string) il nome che indica il tipo di Hibernate |
(3) | force (opzionale - il default è false) "forza" Hibernate a specificare valori consentiti del discriminatore anche quando si stanno recuperando tutte le istanze della classe radice. |
I valori effettivi della colonna discriminatore sono specificati dall'attributo discriminator-value degli elementi <class> e <subclass>.
L'attributo force è utile (solo) se la tabella contiene righe con valori di discriminatore "extra" che non sono mappati su una classe persistente. Questo solitamente non è il caso.
L'elemento <version> è opzionale, e indica che la tabella contiene dati versionati. È particolarmente utile se progettate di usare transazioni lunghe (vedete oltre).
<version column="version_column" (1) name="propertyName" (2) type="typename" (3) access="field|property|ClassName" (4) unsaved-value="null|negative|undefined" (5) />
(1) | column (opzionale - il default è il nome di proprietà): il nome della colonna che mantiene il numero di versione. |
(2) | name: il nome di una proprietà della classe persistente. |
(3) | type (opzionale - il default è integer): il tipo del numero di versione. |
(4) | access (opzionale - il default è property): la strategia che Hibernate deve usare per accedere al valore della proprietà. |
(5) | unsaved-value (opzionale - il default è undefined): il valore di una proprietà di versione che indica che un'istanza è appena stata istanziata (è "unsaved"), distinguendola da istanze transienti che erano state salvate o caricate in una sessione precedente. (undefined specifica che bisogna usare il valore della proprietà identificatore.) |
I numeri di versione possono essere di tipo long, integer, short, timestamp o calendar.
L'elemento opzionale <timestamp> indica che la tabella contiene dati con marche di tempo. Si intende come un'alternativa al versionamento. Le marche di tempo sono per natura un'implementazione meno sicura del locking ottimistico. In ogni caso, a volte l'applicazione potrebbe usare le marche di tempo in altri modi.
<timestamp column="timestamp_column" (1) name="propertyName" (2) access="field|property|ClassName" (3) unsaved-value="null|undefined" (4) />
(1) | column (opzionale - il default è il nome di proprietà): il nome di una colonna che contiene la marca di tempo. |
(2) | name: il nome di una proprietà in stile JavaBeans del tipo java Date o Timestamp della classe persistente. |
(3) | access (opzionale - il default è property): la strategia che Hibernate dovrebbe usare per accedere ai valori delle proprietà. |
(4) | unsaved-value (opzionale - il default è null): il valore di una proprietà di versione che indica che l'istanza è appena stata istanziata (è "unsaved"), distinguendola dalle istanze transienti che sono state savlate o caricate in una sessione precedente. (undefined specifica che bisogna usare il valore della proprietà identificatore.) |
Notate che <timestamp> è equivalente a <version type="timestamp">.
L'elemento <property> dichiara una proprietà persistente in stile JavaBeans della classe.
<property name="propertyName" (1) column="column_name" (2) type="typename" (3) update="true|false" (4) insert="true|false" (4) formula="arbitrary SQL expression" (5) access="field|property|ClassName" (6) />
(1) | name: il nome della proprietà con iniziale minuscola. |
(2) | column (opzionale - il default è usare il nome della proprietà): il nome della colonna mappata della tabella del database. |
(3) | type (opzionale): il nome che indica il tipo di Hibernate. |
(4) | update, insert (opzionale - il default è true): specifica che le colonne mappate dovrebbero essere incluse in istruzioni SQL UPDATE e/o INSERT. Impostare entrambe a false consente una proprietà puramente "derivata" il cui valore è inizializzato da qualche altra proprietà che si mappa sulla stessa colonna (o colonne), o da un trigger o da un'altra applicazione. |
(5) | formula (opzionale): una espressione SQL che definisce il valore per una proprietà calcolata. Le proprietà calcolate non hanno un mappaggio di colonna proprio. |
(6) | access (opzionale - il default è property): la strategia che Hibernate deve usare per accedere al valore della proprietà. |
typename potrebbe essere:
Il nome di un tipo di base di Hibernate (ad esempio. integer, string, character, date, timestamp, float, binary, serializable, object, blob).
Il nome di una classe Java con un tipo di default base (e.g. int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob).
Il nome di una sottoclasse di PersistentEnum (e.g. eg.Color).
Il nome di una classe java serializzabile.
Il nome della classe di un tipo personalizzato (e.g. com.illflow.type.MyCustomType).
Se non specificate un tipo, Hibernate userà la "reflection" sul nome della proprietà per indovinare il tipo di Hibernate corretto. Hibernate cercherà di interpretare il nome della classe di ritorno del metodo recuperatore ("getter") usando le regole 2, 3 e 4 in questo ordine. Però, questo non è sempre abbastanza. In certi casi, avrete comunque bisogno dell'attributo type. (Ad esempio, per distinguere tra Hibernate.DATE e Hibernate.TIMESTAMP, o per specificare un tipo personalizzato.)
L'attributo access vi consente di controllare come Hibernate accederà al valore dell'attributo in fase di esecuzione. Il comportamento predefinito di Hibernate è di chiamare la coppia get/set della proprietà. Se però specificate access="field", Hibernate aggirerà la coppia get/set ed accederà direttamente al campo utilizzando la "reflection". Potete anche specificare la vostra strategia per l'accesso alle proprietà indicando una classe che implementi l'interfaccia net.sf.hibernate.property.PropertyAccessor.
Un'associazione ordinaria ad un'altra classe persistente si dichiara usando un elemento many-to-one. Il modello relazionale è un'associazione molti-a-uno. (In realtà si tratta semplicemente di un riferimento ad oggetto.)
<many-to-one name="propertyName" (1) column="column_name" (2) class="ClassName" (3) cascade="all|none|save-update|delete" (4) outer-join="true|false|auto" (5) update="true|false" (6) insert="true|false" (6) property-ref="propertyNameFromAssociatedClass" (7) access="field|property|ClassName" (8) />
(1) | name: il nome della proprietà. |
(2) | column (opzionale): il nome della colonna. |
(3) | class (opzionale - il default è il tipo della proprietà determinato per "reflection"): il nome della classe associata. |
(4) | cascade (opzionale): specifica quali operazioni dovrebbero andare in cascata dall'oggetto genitore all'oggetto associato. |
(5) | outer-join (opzionale - il default è auto): consente la raccolta via outer-join per questa associazione se è impostata la proprietà hibernate.use_outer_join. |
(6) | update, insert (opzionale - il default è true) specifica che la colonna mappata dovrebbe venire inclusa nelle istruzioni SQL UPDATE e/o INSERT. Impostare entrambe a false consente di avere una associazione puramente "derivata" il cui valore è inizializzato da qualche altra proprietà che si mappi sulla stessa colonna o da un trigger o da un'altra applicazione. |
(7) | property-ref: (opzionale) il nome di una proprietà della classe associata che è messa in join a questa chiave esterna. Se non viene specificata, si usa la chiave primaria della classe associata. |
(8) | access (opzionale - il default è property): la strategia che Hibernate deve usare per accedere al valore di questa proprietà. |
L'attributo cascade accetta i valori seguenti: all, save-update, delete, none. Impostare un valore diverso da none farà sì che certe operazioni si propaghino sull'oggetto associato (figlio). Vedete anche "oggetti a ciclo di vita" più oltre.
L'attributo outer-join accetta tre valori differenti:
auto (default) recupera l'associazione utilizzando un join esterno se la classe associata non ha proxy
true carica sempre l'associazione con un join esterno
false non carica mai l'associazione con un outer join
Una tipica dichiarazione many-to-one appare così semplice:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
L'attributo property-ref dovrebbe venire usato solo per mappare dati preesistenti in cui una chiave esterna faccia riferimento ad una chiave unica della tabella associata che sia diversa dalla chiave primaria. Si tratta di un modello relazionale decisamente orrido. Ad esempio, immaginate che la classe Product abbia un numero di serie unico che non sia la chiave primaria. (L'attributo unique controlla la generazione del DDL da parte di Hibernate con il tool SchemaExport.)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
A questo punto il mappaggio per OrderItem potrebbe usare:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
In ogni modo questo è di certo non incoraggiato.
Una associazione uno-a-uno con un'altra classe persistente si dichiara usando un elemento one-to-one .
<one-to-one name="propertyName" (1) class="ClassName" (2) cascade="all|none|save-update|delete" (3) constrained="true|false" (4) outer-join="true|false|auto" (5) property-ref="propertyNameFromAssociatedClass" (6) access="field|property|ClassName" (7) />
(1) | name: il nome della proprietà. |
(2) | class (opzionale - il default è il tipo della proprietà determinato per "reflection"): il nome della classe associata. |
(3) | cascade (opzionale) specifica quali operazioni dovrebbero propagarsi in cascata dall'oggetto genitore all'oggetto associato. |
(4) | constrained (opzionale) specifica che un vincolo di chiave esterna sulla chiave primaria della tabella mappata fa riferimento alla tabella della classe associata. Questa opzione condiziona l'ordine in cui save() e delete() vengono propagate (ed è anche usata dallo strumento di generazione dello schema ). |
(5) | outer-join (opzionale - il default è auto): consente la raccolta via join esterno per questa associazione quando viene impostato hibernate.use_outer_join. |
(6) | property-ref: (opzionale) il nome di una proprietà della classe associata che è messa in join alla chiave primaria di questa classe. Se non viene specificata, viene usata la chiave primaria della classe associata. |
(7) | access (opzionale - il default è property): la strategia che Hibernate dovrebbe usare per accedere al valore della proprietà. |
Ci sono due varietà di associazioni uno-a-uno:
associazioni di chiave primaria
associazioni di chiave esterna univoca
Le associazioni di chiave primaria non hanno bisogno di una colonna extra nella tabella; se due righe sono messe in relazione dall'associazione, allora le due righe condividono lo stesso valore di chiave primaria. Per questo, se volete che due oggetti siano correlati da un'associazion di chiave primaria, dovete assicurarvi che venga loro assegnato lo stesso valore di identificatore!
Per un'associazione di chiave primaria, si aggiungono i mappaggi seguenti rispettivamente a Employee e Person.
<one-to-one name="person" class="Person"/>
<one-to-one name="employee" class="Employee" constrained="true"/>
Ora ci dobbiamo assicurare che le chiavi primarie delle righe correlate nelle tabelle PERSON e EMPLOYEE siano uguali. Usiamo una strategia di generazione di identificatore speciale di Hibernate, chiamata foreign:
<class name="person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="foreign"> <param name="property">employee</param> </generator> </id> ... <one-to-one name="employee" class="Employee" constrained="true"/> </class>
Ad un'istanza appena salvata di Person si assegna poi lo stesso valore di chiave primaria dell'istanza di Employee a cui fa riferimento la proprietà employee di quella Person.
In alternativa, una chiave esterna con un vincolo di unicità da Employee a Person può essere espressa come:
<many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/>
E questa associazione può essere resa bidirezionale aggiungendo quanto segue al mappaggio di Person:
<one-to-one name"employee" class="Employee" property-ref="person"/>
L'elemento <component> mappa proprietà di un oggetto figlio su colonne della tabella di una classe genitore. I componenti possono, a loro volta, dichiarare le proprie proprietà, componenti o collezioni. Vedete "Componenti" più oltre.
<component name="propertyName" (1) class="className" (2) insert="true|false" (3) upate="true|false" (4) access="field|property|ClassName"> (5) <property ...../> <many-to-one .... /> ........ </component>
(1) | name: il nome della proprietà. |
(2) | class (opzionale - il default è il tipo della proprietà individuato per "reflection"): il nome della classe componente (figlio). |
(3) | insert: le colonne mappate devono apparire nelle INSERT SQL? |
(4) | update: le colonne mappate devono apparire nelle UPDATE SQL? |
(5) | access (opzionale - il default è property): la strategia che Hibernate dovrebbe usare per accedere ai valori delle proprietà. |
I tag figli <property> mappano proprietà della classe figlio a colonne della tabella.
L'elemento <component> consente un sottoelemento <parent> che mappa una proprietà della classe componente come un riferimento all'entità contenitore.
L'elemento <dynamic-component> consente ad una Map di essere mappata come componente in cui i nomi delle proprietà si riferiscono a chiavi della mappa.
Infine, la persistenza polimorfica richiede la dichiarazione di ogni sottoclasse della classe persistente radice. Per la strategia di mappaggio (raccomandata) tabella-per-classe, si usa la dichiarazione <subclass>.
<subclass name="ClassName" (1) discriminator-value="discriminator_value" (2) proxy="ProxyInterface" (3) lazy="true|false" (4) dynamic-update="true|false" dynamic-insert="true|false"> <property .... /> ..... </subclass>
(1) | name: il nomoe di classe completamente qualificato della sottoclasse. |
(2) | discriminator-value (opzionale - il default è il nome della classe): un valore che distingue le sottoclassi individuali. |
(3) | proxy (opzionale): specifica una classe o interfaccia da usare per i mediatori ad inizializzazione ritardata ("lazy initializing proxy"). |
(4) | lazy (opzionale): impostare lazy="true" è un'abbreviazione equivalente a specificare il nome della classe stessa come interfaccia proxy. |
Ogni sottoclasse dovrebbe dichiarare le sue proprietà persistenti e le sottoclassi. Si assume che le proprietà <version> e <id> siano ereditate dalla classe radice. Ogni sottoclasse in una gerarchia deve definire un valore unico di discriminator-value. Se non viene specificato un valore viene usato il nome della classe java completamente qualificato.
In alternativa, una sottoclasse che sia resa persistente sulla sua propria tabella (strategia di mappaggio "tabella per sottoclasse") si dichiara usando un elemento <joined-subclass> element.
<joined-subclass name="ClassName" (1) proxy="ProxyInterface" (2) lazy="true|false" (3) dynamic-update="true|false" dynamic-insert="true|false"> <key .... > <property .... /> ..... </subclass>
(1) | name: il nome di classe completamente qualificato della sottoclasse. |
(2) | proxy (opzionale): specifica una classe od interfaccia da usare per i mediatori a inizializzazione ritardata ("lazy initializing proxy"). |
(3) | lazy (optional): impostare lazy="true" è un'abbreviazione equivalente a specificare il nome della classe stessa come interfaccia proxy. |
Non viene richiesta alcuna colonna discriminatore per questa strategia di mappaggio. Ogni sottoclasse deve, però, dichiarare una colonna della tabella che contiene l'identificatore dell'oggetto usando l'elemento <key>. Il mappaggio all'inizio del capitolo verrebbe riscritto come:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="CATS"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <property name="birthdate" type="date"/> <property name="color" not-null="true"/> <property name="sex" not-null="true"/> <property name="weight"/> <many-to-one name="mate"/> <set name="kittens"> <key column="MOTHER"/> <one-to-many class="Cat"/> </set> <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> <key column="CAT"/> <property name="name" type="string"/> </joined-subclass> </class> <class name="eg.Dog"> <!-- mapping for Dog could go here --> </class> </hibernate-mapping>
Supponete che la vostra applicazione abbia due classi persistenti con lo stesso nome, e non vogliate specificare il nome completamente qualificato (con il package) nelle interrogazioni di Hibernate. Le classi possono essere "importate" esplicitamente invece di fare affidamento a auto-import="true". Potete anche importare classi e interfacce che non sono mappate esplicitamente.
<import class="java.lang.Object" rename="Universe"/>
<import class="ClassName" (1) rename="ShortName" (2) />
(1) | class: il nome completamente qualificato di una classe java qualunque. |
(2) | rename (opzionale - il default è il nome non qualificato della classe): un nome che può essere usato nel linguaggio di interrogazione. |
Per capire il comportamento di vari oggetti java a livello del linguaggio rispetto al servizio di persistenza, dobbiamo classificarli in due gruppi:
Una entity esiste indipendentemente dal fatto che altri oggetti mantengano riferimenti ad essa. Questo è in contrasto con il modello java usuale in cui un oggetto non referenziato è fatto oggetto di garbage collection. Le entità devono essere salvate e cancellate esplicitamente (eccetto il fatto che salvataggi e cancellamenti possono essere cascaded ovvero propagati da un'entità genitore ai suoi figli). Questo è differente dal modello ODMG di persistenza per raggiungibilità degli oggetti - e corrisponde più strettamente a come gli oggetti applicativi sono solitamente usati nei grandi sistemi. Le entità supportano riferimenti circolari e condivisi: possono anche essere versionati.
Lo stato persistente di un'entità consiste di riferimenti ad altre entità e di istanze di tipi di valore. I valori sono tipi primitivi, collezioni componenti e alcuni oggetti immutabili. A differenza delle entità, i tipi di valore (in particolare le collezioni e i componenti) sono resi persistenti e cancellati per raggiungibilità. Poiché gli oggetti di valore (e i primitivi) sono resi persistenti e cancellati insieme all'entità che li contiene, non possono essere versionati indipendentemente. I valori non hanno identità indipendente, e per questo non possono essere condivisi da due entità o collezioni.
Tutti i tipi di Hibernate eccetto le collezioni devono supportare la semantica null.
Fino ad ora, abbiamo usato il termine "classe persistente" per riferirci alle entità. Continueremo a farlo. Parlando esattamente, però, non tutte le classi definite dall'utente con uno stato persistente sono entità. Un componente (component) è una classe definita dall'utente con semantica di valore.
I tipi di base possono a grandi linee essere catalogati in
Mappaggi di tipo dai primitivi java o dalle classi incapsulatore su tipi di colonna SQL appropriati (e specifici della marca del database). boolean, yes_no e true_false sono tutte codifiche alternative per un boolean o java.lang.Boolean di java.
Mappaggio di tipo da java.lang.String a VARCHAR (o VARCHAR2 di Oracle).
Mappaggi di tipo da java.util.Date e le sue sottoclassi ai tipi SQL DATE, TIME e TIMESTAMP (o equivalenti).
Mappaggi di tipo da java.util.Calendar ai tipi SQL TIMESTAMP e DATE (o equivalenti).
Mappaggio di tipo da java.math.BigDecimal a NUMERIC (o NUMBER in Oracle).
Mappaggi di tipo da java.util.Locale, java.util.TimeZone e java.util.Currency verso VARCHAR (o VARCHAR2 in Oracle). Istanze di Locale e Currency vengono mappati sui loro codici ISO. Le istanze di TimeZone vengono mappate sul loro ID.
Mappaggio di tipo da java.lang.Class a VARCHAR (o VARCHAR2 in Oracle). Un oggetto Class viene mappato sul suo nome di classe completamente qualificato.
Mappa array di byte su un tipo binario SQL appropriato.
Mappa stringhe java lunghe su un tipo CLOB or TEXT dell'SQL.
Mappa tipi java serializzabili su un tipo binario SQL appropriato. Potete anche indicare il tipo Hibernate serializable con il nome di una classe java serializzabile o un'interfaccia che non abbia come default un tipo di base, o implementare PersistentEnum.
Mappaggi di tipo per le classi JDBC java.sql.Clob e java.sql.Blob. Questi tipi possono non essere convenienti per alcune applicazioni, perché gli oggetti blob o clob non possono essere riutilizzati al di fuori di una transazione (e per di più il supporto da parte dei driver è approssimativo e inconsistente.)
Gli identificatori unici delle entità e le collezioni possono essere di qualsiasi tipo di base eccetto binary, blob e clob. (Sono permessi anche identificatori compositi, leggete più sotto.)
I tipi di valore di base hanno costanti Type corrispondenti definite nella classe net.sf.hibernate.Hibernate. Ad esempio, Hibernate.STRING rappresenta il tipo string.
Un tipo enumerativo è un idioma java comune in cui una classe ha un (piccolo) numero costante di istanze immutabili. Potete creare un tipo enumerativo persistente implementando net.sf.hibernate.PersistentEnum, definendo le operazioni toInt() e fromInt():
package eg; import net.sf.hibernate.PersistentEnum; public class Color implements PersistentEnum { private final int code; private Color(int code) { this.code = code; } public static final Color TABBY = new Color(0); public static final Color GINGER = new Color(1); public static final Color BLACK = new Color(2); public int toInt() { return code; } public static Color fromInt(int code) { switch (code) { case 0: return TABBY; case 1: return GINGER; case 2: return BLACK; default: throw new RuntimeException("Unknown color code"); } } }
Il nome di tipo di Hibernate in questo caso è semplicemente il nome della classe enumerativa. eg.Color.
Per gli sviluppatori è relativamente facile creare i prorpi tipi di valore. Ad esempio, potreste desiderare rendere persistenti proprietà di tipo java.lang.BigInteger su colonne VARCHAR. Hibernate non fornisce un tipo predefinito per questo, ma i tipi personalizzati non sono limitati al mappaggio di una proprietà (o elemento di collezione) su una singola colonna di tabella. Allora, ad esempio, potreste avere una proprietà java getName()/setName() di tipo java.lang.String che sia resa persistente sulle colonne FIRST_NAME, INITIAL, SURNAME.
Per implementare un tipo personalizzato, implementate net.sf.hibernate.UserType o net.sf.hibernate.CompositeUserType e dichiarate le proprietà usando il nome di classe completamente qualificato del tipo. Guardate il codice di net.sf.hibernate.test.DoubleStringType per vedere il genere di cose che sono possibili.
<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType"> <column name="first_string"/> <column name="second_string"/> </property>
Notate l'uso di elementi <column> per mappare una proprietà su colonne multiple.
Anche se l'insieme ricco di tipi predefiniti e il supporto per i componenti significa che avrete molto raramente bisogno di usare un tipo personalizzato, è comunque considerata una buona norma usare i tipi personalizzati per le classi (non di entità) che si presentino frequentemente nella vostra applicazione. Ad esempio, una classe MonetaryAmount è un buon candidato per un CompositeUserType, anche se potrebbe essere facilmente mappato come componente. Una ragione per questo è l'astrazione. Con un tipo personalizzato i vostri documenti di mappaggio sarebbero a prova di cambiamenti possibili nella vostra maniera di rappresentare valori monetari in futuro.
C'è un tipo ulteriore di mappaggio di proprietà. L'elemento di mappaggio <any> definisce una associazione polimorfica alle classi da tabelle multiple. Questo tipo di mappaggio richiede sempre più di una colonna. La prima colonna mantiene il tipo dell'entità associata. Le colonne rimanenti mantengono l'identificatore. È impossibile specificare un vincolo di chiave esterna per questo genere di associazioni, così non si tratta certamente del modo usuale di mappare associazioni (polimorfiche). Dovreste usarlo solo in casi molto speciali (ad esempio registri di auditing, dati delle sessioni utente, ecc.).
<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType"> <column name="table_name"/> <column name="id"/> </any>
L'attributo meta-type consente all'applicazione di specificare un tipo personalizzato che mappi valori di colonne del database su classi persistenti che abbiano proprietà identificatore del tipo specificato da id-type. Se il meta-tipo restituisce istanze di java.lang.Class, non è richiesto nient'altro. Da un altro punto di vista, se è un tipo basico come string o character, dovete specificare il mappaggio da valori a classi.
<any name="anyEntity" id-type="long" meta-type="string"> <meta-value value="TBL_ANIMAL" class="Animal"/> <meta-value value="TBL_HUMAN" class="Human"/> <meta-value value="TBL_ALIEN" class="Alien"/> <column name="table_name"/> <column name="id"/> </any>
<any name="propertyName" (1) id-type="idtypename" (2) meta-type="metatypename" (3) cascade="none|all|save-update" (4) access="field|property|ClassName" (5) > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any>
(1) | name: il nome della proprietà. |
(2) | id-type: il nome dell'identificatore. |
(3) | meta-type (opzionale - il default è class): un tipo che mappa java.lang.Class su una singola colonna del database o, alternativamente, un tipo che sia consentito per un mappaggio a discriminatore. |
(4) | cascade (opzionale - il default è none): il tipo di cascata. |
(5) | access (opzionale - il default è property): la strategia che Hibernate dovrebbe usare per accedere al valore delle proprietà. |
Il vecchio tipo object che svolgeva un ruolo simile in Hibernate 1.2 è ancora supportato, ma è oramai semi-deprecato.
Potete forzare Hibernate a mettere un identificatore tra virgolette nell'SQL generato mettendo il nome della tabella tra "backtick" (virgolette inverse) nel documento di mappaggio. Hibernate userà lo stile di virgolettatura corretta per il Dialect SQL (di solito sono virgolette doppie, ma SQL Server usa parentesi quadre, e MySQL usa backtick).
<class name="LineItem" table="`Line Item`"> <id name="id" column="`Item Id`"/><generator class="assigned"/></id> <property name="itemNumber" column="`Item #`"/> ... </class>
È possibile definire mappaggi subclass e joined-subclass in documenti di mappaggio separati, direttamente sotto a hibernate-mapping. Questo vi consente di estendere una gerarchia di classe aggiungendo semplicemente un nuovo file di mappaggio. Dovete specificare un attributo extends nel mappaggio di sottoclasse, indicando una superclasse mappata preventivamente: l'uso di questa funzionalità fa sì che l'ordinamento dei documenti di mappaggio sia importante!
<hibernate-mapping> <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>