Capitolo 7. Mappaggio dei componenti

La nozione di un componente viene usata in differenti contesti per scopi diversi, in tutto Hibernate.

7.1. Oggetti dipendenti

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>

7.2. Collezioni di oggetti dipendenti

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à.

7.3. Componenti come indici delle mappe

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.

7.4. Componenti come identificatori composti

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>

7.5. Componenti dinamici

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)