6.2. Mapper une collection

Astuce

Il y a pas mal de variétés de mappings qui peuvent être générés pour les collections, couvrant beaucoup des modèles relationnels communs. Nous vous suggérons d'expérimenter avec l'outil de génération de schéma pour avoir une idée de comment traduire les différentes déclarations de mapping vers des table de la base de données.

L'élément de mapping d'Hibernate utilisé pour mapper une collection dépend du type de l'interface. Par exemple, un élément <set> est utilisé pour mapper des propriétés de type Set.

<class name="Product">
    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class>

À part <set>, il y aussi les éléments de mapping <list>, <map>, <bag>, <array> et <primitive-array>. L'élément <map> est représentatif :

<map
    name="propertyName"                                         (1)
    table="table_name"                                          (2)
    schema="schema_name"                                        (3)
    lazy="true|extra|false"                                     (4)
    inverse="true|false"                                        (5)
    cascade="all|none|save-update|delete|all-delete-orphan|delet(6)e-orphan"
    sort="unsorted|natural|comparatorClass"                     (7)
    order-by="column_name asc|desc"                             (8)
    where="arbitrary sql where condition"                       (9)
    fetch="join|select|subselect"                               (10)
    batch-size="N"                                              (11)
    access="field|property|ClassName"                           (12)
    optimistic-lock="true|false"                                (13)
    mutable="true|false"                                        (14)
    node="element-name|."
    embed-xml="true|false"
>

    <key .... />
    <map-key .... />
    <element .... />
</map>
(1)

name : le nom de la propriété contenant la collection

(2)

table (optionnel - par défaut = nom de la propriété) : le nom de la table de la collection (non utilisé pour les associations one-to-many)

(3)

schema (optionnel) : le nom du schéma pour surcharger le schéma déclaré dans l'élément racine

(4)

lazy (optionnel - par défaut = true) : peut être utilisé pour désactiver l'initialisation tardive et spécifier que l'association est toujours rapportée, ou pour activer la récupération extra-paresseuse (NdT : extra-lazy) où la plupart des opérations n'initialisent pas la collection (approprié pour de très grosses collections)

(5)

inverse (optionnel - par défaut = false) : définit cette collection comme l'extrêmité "inverse" de l'association bidirectionnelle

(6)

cascade (optionnel - par défaut = none) : active les opérations de cascade vers les entités filles

(7)

sort (optionnel) : spécifie une collection triée via un ordre de tri naturel, ou via une classe comparateur donnée (implémentant Comparator)

(8)

order-by (optionnel, seulement à partir du JDK1.4) : spécifie une colonne de table (ou des colonnes) qui définit l'ordre d'itération de Map, Set ou Bag, avec en option asc ou desc

(9)

where (optionnel) : spécifie une condition SQL arbitraire WHERE à utiliser au chargement ou à la suppression d'une collection (utile si la collection ne doit contenir qu'un sous ensemble des données disponibles)

(10)

fetch (optionnel, par défaut = select) : à choisir entre récupération par jointures externes, récupération par selects séquentiels, et récupération par sous-selects séquentiels

(11)

batch-size (optionnel, par défaut = 1) : une taille de batch (batch size) utilisée pour charger plusieurs instances de cette collection en initialisation tardive

(12)

access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété

(13)

optimistic-lock (optionnel - par défaut = true) : spécifie que changer l'état de la collection entraîne l'incrémentation de la version appartenant à l'entité (Pour une association un vers plusieurs, il est souvent raisonnable de désactiver ce paramètre)

(14)

mutable (optionnel - par défaut = true) : une valeur à false spécifie que les éléments de la collection ne changent jamais (une optimisation mineure dans certains cas)

6.2.1. Les clefs étrangères d'une collection

Les instances d'une collection sont distinguées dans la base par la clef étrangère de l'entité qui possède la collection. Cette clef étrangère est référencée comme la(es) colonne(s) de la clef de la collection de la table de la collection. La colonne de la clef de la collection est mappée par l'élément <key>.

Il peut y avoir une contrainte de nullité sur la colonne de la clef étrangère. Pour les associations unidirectionnelles un vers plusieurs, la colonne de la clef étrangère peut être nulle par défaut, donc vous pourriez avoir besoin de spécifier not-null="true".

<key column="productSerialNumber" not-null="true"/>

La contraite de la clef étrangère peut utiliser ON DELETE CASCADE.

<key column="productSerialNumber" on-delete="cascade"/>

Voir le chapitre précédent pour une définition complète de l'élément <key>.

6.2.2. Les éléments d'une collection

Les collections peuvent contenir la plupart des autres types Hibernate, dont tous les types basiques, les types utilisateur, les composants, et bien sûr, les références vers d'autres entités. C'est une distinction importante : un objet dans une collection pourrait être géré avec une sémantique de "valeur" (sa durée de vie dépend complètement du propriétaire de la collection) ou il pourrait avoir une référence vers une autre entité, avec sa propre durée de vie. Dans le dernier cas, seul le "lien" entre les 2 objets est considéré être l'état retenu par la collection.

Le type contenu est référencé comme le type de l'élément de la collection. Les éléments de la collections sont mappés par <element> ou <composite-element>, ou dans le cas des références d'entité, avec <one-to-many> ou <many-to-many>. Les deux premiers mappent des éléments avec un sémantique de valeur, les deux suivants sont utilisés pour mapper des associations d'entité.

6.2.3. Collections indexées

Tous les mappings de collection, exceptés ceux avec les sémantiques d'ensemble (NdT : set) et de sac (NdT : bag), ont besoin d'une colonne d'index dans la table de la collection - une colonne qui mappe un index de tableau, ou un index de List, ou une clef de Map. L'index d'une Map peut être n'importe quel type basique, mappé avec <map-key>, ça peut être une référence d'entité mappée avec <map-key-many-to-many>, ou ça peut être un type composé, mappé avec <composite-map-key>. L'index d'un tableau ou d'une liste est toujours de type integer et est mappé en utilisant l'élément <list-index>. Les colonnes mappées contiennent des entiers séquentiels (numérotés à partir de zéro par défaut).

<list-index 
        column="column_name"                (1)
        base="0|1|..."/>
(1)

column_name (required): The name of the column holding the collection index values.

(1)

base (optional, defaults to 0): The value of the index column that corresponds to the first element of the list or array.

<map-key 
        column="column_name"                (1)
        formula="any SQL expression"        (2)
        type="type_name"                    (3)
        node="@attribute-name"
        length="N"/>
(1)

column (optional): The name of the column holding the collection index values.

(2)

formula (optional): A SQL formula used to evaluate the key of the map.

(3)

type (reguired): The type of the map keys.

<map-key-many-to-many
        column="column_name"                (1)
        formula="any SQL expression"        (2)(3)
        class="ClassName"
/>
(1)

column (optional): The name of the foreign key column for the collection index values.

(2)

formula (optional): A SQL formula used to evaluate the foreign key of the map key.

(3)

class (required): The entity class used as the map key.

Si votre table n'a pas de colonne d'index, et que vous souhaitez tout de même utiliser List comme type de propriété, vous devriez mapper la propriété comme un <bag> Hibernate. Un sac (NdT : bag) ne garde pas son ordre quand il est récupéré de la base de données, mais il peut être optionnellement trié ou ordonné.

6.2.4. Collections de valeurs et associations plusieurs-vers-plusieurs

N'importe quelle collection de valeurs ou association plusieurs-vers-plusieurs requiert une table de collection avec une(des) colonne(s) de clef étrangère, une(des) colonne(s) d'élément de la collection ou des colonnes et possiblement une(des) colonne(s) d'index.

Pour une collection de valeurs, nous utilisons la balise <element>.

<element
        column="column_name"                     (1)
        formula="any SQL expression"             (2)
        type="typename"                          (3)
        length="L"
        precision="P"
        scale="S"
        not-null="true|false"
        unique="true|false"
        node="element-name"
/>
(1)

column (optional): The name of the column holding the collection element values.

(2)

formula (optional): An SQL formula used to evaluate the element.

(3)

type (required): The type of the collection element.

A many-to-many association is specified using the <many-to-many> element.

<many-to-many
        column="column_name"                               (1)
        formula="any SQL expression"                       (2)
        class="ClassName"                                  (3)
        fetch="select|join"                                (4)
        unique="true|false"                                (5)
        not-found="ignore|exception"                       (6)
        entity-name="EntityName"                           (7)
        property-ref="propertyNameFromAssociatedClass"     (8)
        node="element-name"
        embed-xml="true|false"
    />
(1)

column (optional): The name of the element foreign key column.

(2)

formula (optional): An SQL formula used to evaluate the element foreign key value.

(3)

class (required): The name of the associated class.

(4)

fetch (optional - defaults to join): enables outer-join or sequential select fetching for this association. This is a special case; for full eager fetching (in a single SELECT) of an entity and its many-to-many relationships to other entities, you would enable join fetching not only of the collection itself, but also with this attribute on the <many-to-many> nested element.

(5)

unique (optional): Enable the DDL generation of a unique constraint for the foreign-key column. This makes the association multiplicity effectively one to many.

(6)

not-found (optional - defaults to exception): Specifies how foreign keys that reference missing rows will be handled: ignore will treat a missing row as a null association.

(7)

entity-name (optional): The entity name of the associated class, as an alternative to class.

(8)

property-ref: (optional) The name of a property of the associated class that is joined to this foreign key. If not specified, the primary key of the associated class is used.

Quelques exemples, d'abord, un ensemble de chaînes de caractères :

<set name="names" table="person_names">
    <key column="person_id"/>
    <element column="person_name" type="string"/>
</set>

Un bag contenant des entiers (avec un ordre d'itération déterminé par l'attribut order-by) :

<bag name="sizes" 
        table="item_sizes" 
        order-by="size asc">
    <key column="item_id"/>
    <element column="size" type="integer"/>
</bag>

Un tableau d'entités - dans ce cas, une association plusieurs-vers-plusieurs :

<array name="addresses" 
        table="PersonAddress" 
        cascade="persist">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array>

Une map de chaînes de caractères vers des dates :

<map name="holidays" 
        table="holidays" 
        schema="dbo" 
        order-by="hol_name asc">
    <key column="id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

Une liste de composants (discute dans le prochain chapitre) :

<list name="carComponents" 
        table="CarComponents">
    <key column="carId"/>
    <list-index column="sortOrder"/>
    <composite-element class="CarComponent">
        <property name="price"/>
        <property name="type"/>
        <property name="serialNumber" column="serialNum"/>
    </composite-element>
</list>

6.2.5. Association un-vers-plusieurs

Une association un vers plusieurs lie les tables de deux classes par une clef étrangère, sans l'intervention d'une table de collection. Ce mapping perd certaines sémantiques des collections Java normales :

  • Une instance de la classe de l'entité contenue ne peut pas appartenir à plus d'une instance de la collection

  • Une instance de la classe de l'entité contenue ne peut pas apparaître plus plus d'une valeur d'index de la collection

Une association de Product vers Part requiert l'existence d'une clef étrangère et possiblement une colonne d'index pour la table Part. Une balise <one-to-many> indique que c'est une association un vers plusieurs.

<one-to-many 
        class="ClassName"                                  (1)
        not-found="ignore|exception"                       (2)
        entity-name="EntityName"                           (3)
        node="element-name"
        embed-xml="true|false"
    />
(1)

class (requis) : le nom de la classe associée

(2)

not-found (optionnel - par défaut exception) : spécifie comment les identifiants cachés qui référencent des lignes manquantes seront gérés : ignore traitera une ligne manquante comme une association nulle

(3)

entity-name (optional): The entity name of the associated class, as an alternative to class.

Notez que l'élément <one-to-many> n'a pas besoin de déclarer de colonnes. Il n'est pas non plus nécessaire de spécifier le nom de la table nulle part.

Note très importante : si la colonne de la clef d'une association <one-to-many> est déclarée NOT NULL, vous devez déclarer le mapping de <key> avec not-null="true" ou utiliser une association bidirectionnelle avec le mapping de la collection marqué inverse="true". Voir la discussion sur les associations bidirectionnelles plus tard dans ce chapitre.

Cet exemple montre une map d'entités Part par nom (où partName est une propriété persistante de Part). Notez l'utilisation d'un index basé sur une formule.

<map name="parts"
        cascade="all">
    <key column="productId" not-null="true"/>
    <map-key formula="partName"/>
    <one-to-many class="Part"/>
</map>