La nozione di un componente viene usata in differenti contesti per scopi diversi, in tutto Hibernate.
Un componente è un oggetto contenuto, che viene reso persistente come un tipo di valore ("value type"), non un'entità. Il termine "componente" si riferisce al concetto "orientato agli oggetti" della composizione (non a componenti di livello architetturale). Per esempio, potreste modellare una persona come segue:
public class Person { private java.util.Date birthday; private Name name; private String key; public String getKey() { return key; } private void setKey(String key) { this.key=key; } public java.util.Date getBirthday() { return birthday; } public void setBirthday(java.util.Date birthday) { this.birthday = birthday; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } ...... ...... }
public class Name { char initial; String first; String last; public String getFirst() { return first; } void setFirst(String first) { this.first = first; } public String getLast() { return last; } void setLast(String last) { this.last = last; } public char getInitial() { return initial; } void setInitial(char initial) { this.initial = initial; } }
Ora Name può essere reso persistente come un componente di Person. Notate che Name definisce metodi "getter" e "setter" per le sue proprietà persistenti, ma non deve dichiarare alcuna interfaccia o proprietà identificatore.
Il nostro mappaggio Hibernate avrebbe questo aspetto:
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid.hex"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- l'attributo class è opzionale --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
La tabella "person" avrebbe le colonne pid, birthday, initial, first e last.
Come tutti i tipi di valore, i componenti non supportano riferimenti condivisi. La semantica di valore nullo di un componente è ad hoc. Quando si ricarica l'oggetto contenitore, Hibernate supporrà che se tutte le colonne del componente sono nulle, allora l'intero componente è nullo. Questo dovrebbe adattarsi alla maggior parte degli scopi.
Le proprietà di un componente possono essere di un tipo qualunque di Hibernate (collezioni associazioni molti-a-uno, altri componenti, ecc.). Componenti annidati non dovrebbero essere considerati un utilizzo esotico. Hibernate è pensato per supportare un modello ad oggetti a grana molto fine.
L'elemento <component> consente di usare un sotto-elemento <parent> che mappa la proprietà di una classe componente come un riferimento "indietro" all'entità contenitore.
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid.hex"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <parent name="namedPerson"/> <!-- retro-riferimento all'oggetto Person --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
Le collezioni di componenti sono permesse (ad esempio un array di tipo Name). Dichiarate le collezioni di componenti rimpiazzando l'etichetta <element> con una <composite-element>.
<set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- l'attributo class è obbligatorio --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element> </set>
Nota: se definite un Set di elementi composti, è molto importante definire correttamente equals() e hashCode() correctly.
Gli elementi composti possono contenere componenti ma non collezioni. Se il vostro elemento composto contiene componenti, usate l'etichetta <nested-composite-element>. Si tratta di un caso abbastanza esotico - una collezione di componenti che a loro volta hanno componenti. A questo stadio dovreste chiedervi se una associazione uno-a-molti non sia più appropriata. Provate a rimodellare l'elemento composto come una entità - ma notate che anche se il modello java è lo stesso, il modello relazionale e la semantica di persistenza sono leggermente diversi.
Tenete presente che un mappaggio ad elemento composto non supporta proprietà nulle se state usando un <set>. Hibernate deve usare ogni colonna per identificare un record quando cancella oggetti (non c'è una colonna separata di chiave primaria, nella tabella dell'elemento composto), cosa che non è possibile con valori nulli. In un composite-element dovete usare solo proprietà non nulle o scegliere una <list>, <map>, <bag> o <idbag>.
Un caso speciale di elemento composto è quello in cui l'elemento stesso ha un altro elemento annidato <many-to-one>. Un mappaggio di questo tipo, vi consente di mappare colonne extra di una tabella molti-a-molti sulla classe dell'elemento composto. Qui di seguito mostriamo una associazione molti-a-molti da Order a Item in cui purchaseDate, price e quantity sono proprietà dell'associazione:
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.Purchase"> <property name="purchaseDate"/> <property name="price"/> <property name="quantity"/> <many-to-one name="item" class="eg.Item"/> <!-- l'attributo class è opzionale --> </composite-element> </set> </class>
Sono possibili anche associazioni ternarie (o quaternarie, ecc):
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.OrderLine"> <many-to-one name="purchaseDetails class="eg.Purchase"/> <many-to-one name="item" class="eg.Item"/> </composite-element> </set> </class>
Gli elementi composti possono apparire nelle query usando la stessa sintassi delle associazioni ad altre entità.
L'elemento <composite-index> vi consente di mappare una classe di componente come chiave di una Map. Assicuratevi di implementare hashCode() e equals() correttamente sulla classe componente, in questo caso.
Potete usare un componente come un identificatore di una classe di entità. La vostra classe di componente deve soddisfare alcuni requisiti:
Deve implementare java.io.Serializable.
Deve re-implementare equals() and hashCode(), consistentemente con la nozione di uguaglianza di chiave sul database.
Non potete usare un IdentifierGenerator per generare chiavi composte. Al contrario, sarà l'applicazione che deve assegnare i propri identificatori.
Poiché un identificatore composto deve venire assegnato all'oggetto prima di salvarlo, non possiamo usare un "valore non salvato" (unsaved-value) sull'identificatore per distinguere tra istanze appena istanziate e istanze salvate in una sessione precedente.
Se volete usare saveOrUpdate() o save / update in cascata, potete invece implementare Interceptor.isUnsaved() . In alternativa, potete anche impostare l'attributo unsaved-value su un elemento <version> (o <timestamp>) per specificare il valore che identifica una nuova istanza transiente. In questo caso, viene usata la versione dell'entità invece dell'identificatore (assegnato), e non dovete essere voi ad implementare Interceptor.isUnsaved().
Per dichiarare un identificatore di classe composta, usate l'elemento <composite-id> (con gli stessi attributi ed elementi di <component>) al posto di <id>:
<class name="eg.Foo" table"FOOS"> <composite-id name="compId" class="eg.FooCompositeID"> <key-property name="string"/> <key-property name="short"/> <key-property name="date" column="date_" type="date"/> </composite-id> <property name="name"/> .... </class>
Ora, qualsiasi chiave esterna verso la tabella FOOS deve necessariamente essere composta, e dovete dichiararlo nei vostri mappaggi delle altre classi. Una associazione verso Foo verrà dichiarata in questo modo:
<many-to-one name="foo" class="eg.Foo"> <!-- come sempre l'attributo "class" è opzionale --> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </many-to-one>
Questo nuovo elemento <column> viene anche usato dai tipi personalizzati multi-colonna. In effetti è ovunque un'alternativa all'attributo column. Una collezione con elementi di tipo Foo utilizzerebbe:
<set name="foos"> <key column="owner_id"/> <many-to-many class="eg.Foo"> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </many-to-many> </set>
Dall'altro lato, <one-to-many>, non dichiara colonne, come sempre.
Se lo stesso Foo contiene collezioni, anch'esse richiederanno una chiave esterna composta.
<class name="eg.Foo"> .... .... <set name="dates" lazy="true"> <key> <!-- la collezione eredita il tipo della chiave composta --> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </key> <element column="foo_date" type="date"/> </set> </class>
Potete anche mappare una proprietà di tipo Map:
<dynamic-component name="userAttributes"> <property name="foo" column="FOO"/> <property name="bar" column="BAR"/> <many-to-one name="baz" class="eg.Baz" column="BAZ"/> </dynamic-component>
La semantica di un mappaggio <dynamic-component> è identica a <component>. Il vantaggio di questo tipo di mappaggio è la capacità di determinare le vere proprietà del bean in fase di messa in esecuzione, semplicemente cambiando il documento di mappaggio. ( È anche possibile manipolare in fase di esecuzione il documento di mappaggio usando un parser DOM)