Hibernate.orgCommunity Documentation

Chapitre 9. Mappage de composants

9.1. Objets dépendants
9.2. Collection d'objets dépendants
9.3. Les composants en tant qu'indices de Map
9.4. Les composants en tant qu'identifiants composites
9.5. Les composants dynamiques

La notion de composants est réutilisée dans différents contextes, avec différents objectifs, à travers Hibernate.

Le composant est un objet inclus dans un autre objet, sauvegardé en tant que type valeur, et non en tant que référence entité. Le terme "composant" fait référence à la notion (au sens objet) de composition et non pas de composant au sens d'architecture de composants. Par exemple, on pourrait modéliser l'objet personne de la façon suivante :

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;
    }
}

Maintenant Name pourra être sauvegardé en tant que composant de Person. Remarquez que Name définit des méthodes getter et setter pour ses propriétés persistantes, mais ne doit déclarer aucune interface ou propriété d'identification.

Dans Hibernate le mappage du composant serait :


<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name"
> <!-- class attribute optional -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class
>

La table "person" aurait les colonnes pid, birthday, initial, first et last.

Comme tous les types valeurs, les composants ne supportent pas les références partagées. En d'autres termes, deux instances de person peuvent avoir un même nom, mais ces noms sont indépendants, ils peuvent être identiques si on les compare par valeur mais ils représentent deux objets distincts en mémoire. La sémantique de la valeur null d'un composant est ad hoc. Quand il recharge l'objet qui contient le composant, Hibernate suppose que si toutes les colonnes de composants sont nulles, le composant est positionné à la valeur null. Ce choix programmatif devrait être satisfaisant dans la plupart des cas.

Les propriétés d'un composant peuvent être de tous les types habituellement supportés par Hibernate (collections, associations plusieurs-à-un, autres composants, etc). Les composants imbriqués ne doivent pas être vus comme quelque chose d'exotique. Hibernate a été conçu pour supporter un modèle d'objet finement granulé.

L'élément <component> permet de déclarer un sous-élément <parent> qui associe une propriété de la classe composant comme une référence arrière vers l'entité contenante.


<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name" unique="true">
        <parent name="namedPerson"/> <!-- reference back to the Person -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class
>

Les collections d'objets dépendants sont supportées (exemple: un tableau de type Name). Déclarez votre collection de composants en remplaçant la balise <element> par la balise <composite-element> :


<set name="someNames" table="some_names" lazy="true">
    <key column="id"/>
    <composite-element class="eg.Name"
> <!-- class attribute required -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </composite-element>
</set
>

Les éléments composites peuvent aussi contenir des composants mais pas des collections. Si votre élément composite contient aussi des composants, utilisez la balise <nested-composite-element>. Une collection de composants qui contiennent eux-mêmes des composants est un cas très exotique. A ce stade, demandez-vous si une association un-à-plusieurs ne serait pas plus appropriée. Essayez de remodeler votre élément composite comme une entité - remarquez que si le modèle Java est le même, toutefois le modèle relationnel et la sémantique de persistance diffèrent quelque peu.

Remarquez que le mappage d'éléments composites ne supporte pas la nullité des propriétés lorsqu'on utilise un <set>. Hibernate lorsqu'il supprime un objet, utilise chaque colonne pour identifier un objet (il n'y a pas de colonne distincte de clés primaires dans la table d'éléments composites), ce qui n'est pas possible avec des valeurs nulles. Vous devez donc choisir d'interdire la nullité des propriétés d'un élément composite ou choisir un autre type de collection comme : <list>, <map>, <bag> ou <idbag>.

Un cas particulier d'élément composite est un élément composite qui inclut un élément imbriqué <many-to-one>. Un mappage comme celui-ci vous permet d'associer des colonnes supplémentaires d'une table d'association plusieurs à plusieurs à la classe de l'élément composite. L'exemple suivant est une association plusieurs à plusieurs de Order à ItempurchaseDate, price et quantity sont des propriétés de l'association :


<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"/> <!-- class attribute is optional -->
        </composite-element>
    </set>
</class
>

Par ailleurs, on ne peut évidemment pas faire référence à l'achat (purchase), pour pouvoir naviguer de façon bidirectionnelle dans l'association. N'oubliez pas que les composants sont de type valeurs et n'autorisent pas les références partagées. Un Purchase unique peut être dans le set d'un Order, mais ne peut pas être référencé par Item simultanément.

Même les associations ternaires, quaternaires ou autres sont possibles :


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

Des éléments composites peuvent apparaître dans les requêtes en utilisant la même syntaxe que les associations vers d'autres entités.

L'élément <composite-map-key> vous permet de mapper une classe de composant comme indice d'une Map. Assurez-vous de surcharger correctement hashCode() et equals() dans la classe du composant.

Vous pouvez utiliser un composant comme identifiant d'une classe entité. À cet effet, votre classe de composant doit respecter certaines exigences :

Vous ne pouvez pas utiliser de IdentifierGenerator pour générer des clés composites, par contre l'application doit assigner ses propres identifiants.

Utiliser la balise <composite-id> (avec les éléments imbriqués <key-property>) à la place de l'habituel déclaration <id>. Par exemple, la classe OrderLine possède une clé primaire qui dépend de la clé primaire (composite) de Order.


<class name="OrderLine">

    <composite-id name="id" class="OrderLineId">
        <key-property name="lineId"/>
        <key-property name="orderId"/>
        <key-property name="customerId"/>
    </composite-id>

    <property name="name"/>

    <many-to-one name="order" class="Order"
            insert="false" update="false">
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-one>
    ....

</class
>

Toutes les clés étrangères référençant la table OrderLine sont également composites. Vous devez en tenir compte lorsque vous écrivez vos mappage d'association pour les autres classes. Une association à OrderLine sera mappée de la façon suivante :


<many-to-one name="orderLine" class="OrderLine">
<!-- the "class" attribute is optional, as usual -->
    <column name="lineId"/>
    <column name="orderId"/>
    <column name="customerId"/>
</many-to-one
>

Une association plusieurs-à-plusieurs à OrderLine utilisera aussi une clé étrangère composite :


<set name="undeliveredOrderLines">
    <key column name="warehouseId"/>
    <many-to-many class="OrderLine">
        <column name="lineId"/>
        <column name="orderId"/>
        <column name="customerId"/>
    </many-to-many>
</set
>

La collection des OrderLine s dans Order utilisera :


<set name="orderLines" inverse="true">
    <key>
        <column name="orderId"/>
        <column name="customerId"/>
    </key>
    <one-to-many class="OrderLine"/>
</set
>

Comme d'habitude, l'élément <one-to-many> ne déclare pas de colonne.

Si OrderLine lui-même possède une collection, il possédera de même une clé composite étrangère.


<class name="OrderLine">
    ....
    ....
    <list name="deliveryAttempts">
        <key
>   <!-- a collection inherits the composite key type -->
            <column name="lineId"/>
            <column name="orderId"/>
            <column name="customerId"/>
        </key>
        <list-index column="attemptId" base="1"/>
        <composite-element class="DeliveryAttempt">
            ...
        </composite-element>
    </set>
</class
>

Vous pouvez également mapper une propriété de type Map :


<dynamic-component name="userAttributes">
    <property name="foo" column="FOO" type="string"/>
    <property name="bar" column="BAR" type="integer"/>
    <many-to-one name="baz" class="Baz" column="BAZ_ID"/>
</dynamic-component
>

La sémantique de l'association à un <dynamic-component> est identique à celle que l'on utilise pour le <component>. L'avantage de ce type de mappage est qu'il permet de déterminer les véritables propriétés du bean au moment du déploiement, en éditant simplement le document de mappage. La manipulation du document de mappage pendant l'exécution de l'application est aussi possible en utilisant un parser DOM. Il y a même mieux, vous pouvez accéder (et changer) le métamodèle de configuration-temps de Hibernate en utilisant l'objet Configuration.