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)