Hibernate.orgCommunity Documentation

Chapitre 6. Mapper une collection

6.1. Collections persistantes
6.2. Mapper une collection
6.2.1. Les clés étrangères d'une collection
6.2.2. Les éléments d'une collection
6.2.3. Collections indexées
6.2.4. Collections de valeurs et associations plusieurs-à-plusieurs
6.2.5. Associations un-à-plusieurs
6.3. Mappages de collection avancés
6.3.1. Collections triées
6.3.2. Associations bidirectionnelles
6.3.3. Associations bidirectionnelles avec des collections indexées
6.3.4. Associations ternaires
6.3.5. Using an <idbag>
6.4. Exemples de collections

Hibernate requiert que les champs contenant des collections persistantes soient déclarés comme des types d'interface, par exemple :

public class Product {

    private String serialNumber;
    private Set parts = new HashSet();
    
    public Set getParts() { return parts; }
    void setParts(Set parts) { this.parts = parts; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

L'interface réelle peut être java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap ou n'importe quoi d'autre ! (Où "n'importe quoi d'autre" signifie que vous devrez écrire une implémentation de org.hibernate.usertype.UserCollectionType.)

Notez comment nous avons initialisé la variable d'instance avec une instance de HashSet. C'est le meilleur moyen pour initialiser les collections d'instances nouvellement créées (non persistantes). Quand nous fabriquons l'instance persistante - en appelant persist(), par exemple - Hibernate remplacera réellement le HashSet par une instance d'une implémentation propre à Hibernate de Set. Prenez garde aux erreurs suivantes :

Cat cat = new DomesticCat();

Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Okay, kittens collection is a Set
(HashSet) cat.getKittens(); // Error!

Les collections persistantes injectées par Hibernate se comportent de la même manière que HashMap, HashSet, TreeMap, TreeSet ou ArrayList, selon le type de l'interface.

Les instances des collections ont le comportement habituel des types de valeurs. Elles sont automatiquement persistées quand elles sont référencées par un objet persistant et automatiquement effacées quand elles sont déréférencées. Si une collection est passée d'un objet persistant à un autre, ses éléments peuvent être déplacés d'une table à une autre. Deux entités ne peuvent pas partager une référence vers une même instance de collection. Dû au modèle relationnel sous-jacent, les propriétés contenant des collections ne supportent pas la sémantique de la valeur null ; Hibernate ne fait pas de distinction entre une référence de collection nulle et une collection vide.

Ne vous en souciez pas trop. Utilisez les collections persistantes de la même manière que vous utilisez des collections Java ordinaires. Assurez-vous de comprendre la sémantique des associations bidirectionnelles (traitée plus loin).

L'élément de mappage 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 mappage <list>, <map>, <bag>, <array> et <primitive-array>. L'élément <map> est représentatif :

<map
    name="prop(1)ertyName"
    table="tab(2)le_name"
    schema="sc(3)hema_name"
    lazy="true(4)|extra|false"
    inverse="t(5)rue|false"
    cascade="a(6)ll|none|save-update|delete|all-delete-orphan|delete-orphan"
    sort="unso(7)rted|natural|comparatorClass"
    order-by="(8)column_name asc|desc"
    where="arb(9)itrary sql where condition"
    fetch="joi(10)n|select|subselect"
    batch-size(11)="N"
    access="fi(12)eld|property|ClassName"
    optimistic(13)-lock="true|false"
    mutable="t(14)rue|false"
    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 un-à-plusieurs)

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

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" utilisée pour charger plusieurs instances de cette collection.

12

access (optionnel - par défaut = property) : la stratégie que 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 des résultats de la collection entraîne l'incrémentation de la version appartenant à l'entité (Pour une association un-à-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).

Tous les mappages de collection, exceptés ceux avec les sémantiques d'ensemble (set) et de sac (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 clé de Map. L'index d'une Map peut être n'importe quel type basique, mappé avec <map-key>, ou peut être une référence d'entité mappée avec <map-key-many-to-many>, ou 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(1)="column_name"
        base="(2)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(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)type_name"
        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 (required): the type of the map keys.

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

1

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

2

formula (optional): a SQ 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 (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é.

Toute collection de valeurs ou association plusieurs-à-plusieurs requiert une table de collection avec une(des) colonne(s) de clé étrangère, une(des) colonne(s) d'élément de la collection ou des colonnes et éventuellement une(des) colonne(s) d'index.

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

<element
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)typename"
        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(1)="column_name"
        formul(2)a="any SQL expression"
        class=(3)"ClassName"
        fetch=(4)"select|join"
        unique(5)="true|false"
        not-fo(6)und="ignore|exception"
        entity(7)-name="EntityName"
        proper(8)ty-ref="propertyNameFromAssociatedClass"
        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): enables 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.

Voici quelques exemples :

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 sac 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-à-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 (traité 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
>

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

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

<one-to-many
        class=(1)"ClassName"
        not-fo(2)und="ignore|exception"
        entity(3)-name="EntityName"
        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 (optionnel) : le nom de l'entité de la classe associée, comme une alternative à 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 à aucun endroit.

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
>

Hibernate supporte des collections implémentant java.util.SortedMap et java.util.SortedSet. Vous devez spécifier un comparateur dans le fichier de mappage :


<set name="aliases"
            table="person_aliases" 
            sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map
>

Les valeurs permises pour l'attribut sort sont unsorted, natural et le nom d'une classe implémentant java.util.Comparator.

Les collections triées se comportent réellement comme java.util.TreeSet ou java.util.TreeMap.

Si vous voulez que la base de données elle-même ordonne les éléments de la collection, utilisez l'attribut order-by des mappages set, bag ou map. Cette solution est seulement disponible à partir du JDK 1.4 (c'est implémenté en utilisant LinkedHashSet ou LinkedHashMap). Ceci exécute le tri dans la requête SQL, pas en mémoire.


<set name="aliases" table="person_aliases" order-by="lower(name) asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map
>

Les associations peuvent même être triées sur des critères arbitraires à l'exécution en utilisant un filter() de collection :

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

Une association bidirectionnelle permet la navigation à partir des deux extrémités de l'association. Deux types d'associations bidirectionnelles sont supportées :

Vous pouvez spécifier une association bidirectionnelle plusieurs-à-plusieurs simplement en mappant deux associations plusieurs-à-plusieurs vers la même table de base de données et en déclarant une extrémité comme inverse (celle de votre choix, mais pas une collection indexée).

Voici un exemple d'association bidirectionnelle plusieurs-à-plusieurs ; chaque catégorie peut avoir plusieurs objets et chaque objet peut être dans plusieurs catégories :


<class name="Category">
    <id name="id" column="CATEGORY_ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="Item">
    <id name="id" column="ITEM_ID"/>
    ...

    <!-- inverse end -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class
>

Les changements faits uniquement sur l'extrémité inverse de l'association ne sont pas persistés. Ceci signifie qu'Hibernate a deux représentations en mémoire pour chaque association bidirectionnelle, un lien de A vers B et un autre de B vers A. Ceci est plus facile à comprendre si vous pensez au modèle objet de Java et à la façon dont nous créons une relation plusieurs-à-plusieurs dans Java :



category.getItems().add(item);          // The category now "knows" about the relationship
item.getCategories().add(category);     // The item now "knows" about the relationship
session.persist(item);                   // The relationship won't be saved!
session.persist(category);               // The relationship will be saved

La partie non-inverse est utilisée pour sauvegarder la représentation en mémoire dans la base de données.

Vous pouvez définir une association bidirectionnelle un-à-plusieurs en mappant une association un-à-plusieurs vers la(es) même(s) colonne(s) de table qu'une association plusieurs-à-un et en déclarant l'extrémité pluri-valuée inverse="true".


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <set name="children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Mapper une extrémité d'une association avec inverse="true" n'affecte pas l'opération de cascades, ce sont des concepts orthogonaux.

Une association bidirectionnelle où une extrémité est représentée comme une <list> ou une <map> requiert une considération spéciale. S'il y a une propriété de la classe enfant qui mappe la colonne de l'index, pas de problème, nous pouvons continuer à utiliser inverse="true" sur le mappage de la collection :


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <property name="name" 
        not-null="true"/>
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Mais, si il n'y a pas de telle propriété sur la classe enfant, nous ne pouvons pas considérer l'association comme vraiment bidirectionnelle (il y a des informations disponibles à une extrémité de l'association qui ne sont pas disponibles à l'autre extrémité). Dans ce cas, nous ne pouvons pas mapper la collection inverse="true". Par contre, nous utiliserons le mappage suivant :


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        insert="false"
        update="false"
        not-null="true"/>
</class
>

Note that in this mapping, the collection-valued end of the association is responsible for updates to the foreign key.

Si vous êtes bien d'accord avec nous sur le fait que les clés composées sont une mauvaise chose et que les entités devraient avoir des identifiants artificiels (des clés subrogées), vous pourrez trouver un peu curieux que les associations plusieurs-à-plusieurs et les collections de valeurs que nous avons montrées jusqu'ici, mappent toutes des tables avec des clés composées ! Il est vrai que ce point est ambigu ; une table d'association pure ne semble pas tirer avantage d'une clé subrogée (bien qu'une collection de valeur composées le pourrait). Néanmoins, Hibernate fournit une fonctionnalité qui vous permet de mapper des associations plusieurs-à-plusieurs et des collections de valeurs vers une table avec une clé subrogée.

L'élément <idbag> vous laisse mapper une List (ou une Collection) avec une sémantique de sac. Par exemple :


<idbag name="lovers" table="LOVERS">
    <collection-id column="ID" type="long">
        <generator class="sequence"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag
>

Comme vous pouvez le constater, un <idbag> a un générateur d'id artificiel, exactement comme une classe d'entité ! Une clé subrogée différente est assignée à chaque ligne de la collection. Cependant, Hibernate ne fournit pas de mécanisme pour découvrir la valeur d'une clé subrogée d'une ligne particulière.

Notez que les performances de la mise à jour d'un <idbag> sont bien meilleures qu'un <bag> ordinaire ! Hibernate peut localiser des lignes individuelles efficacement et les mettre à jour ou les effacer individuellement, comme une liste, une map ou un ensemble.

Dans l'implémentation actuelle, la stratégie de la génération de l'identifiant native n'est pas supportée pour les identifiants de collection <idbag>.

Exemples de collections

La classe suivante possède une collection d'instances Child(filles) :

package eg;

import java.util.Set;
public class Parent {
    private long id;
    private Set children;
    public long getId() { return id; }
    private void setId(long id) { this.id=id; }
    private Set getChildren() { return children; }
    private void setChildren(Set children) { this.children=children; }
    ....
    ....
}

Si chaque instance fille a au plus un parent, le mappage le plus naturel est une association un-à-plusieurs :


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

Ceci mappe les définitions de tables suivantes :


create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

Si le parent est requis, utilisez une association bidirectionnelle un-à-plusieurs :


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping
>

Notez la contrainte NOT NULL :


create table parent ( id bigint not null primary key )
create table child ( id bigint not null
                     primary key,
                     name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

Alternativement, si vous insistez absolument pour que cette association soit unidirectionnelle, vous pouvez déclarer la contrainte NOT NULL sur le mappage <key> :


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

D'autre part, si un enfant peut avoir plusieurs parents, une association plusieurs-à-plusieurs est plus appropriée :


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" table="childset">
            <key column="parent_id"/>
            <many-to-many class="Child" column="child_id"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

Définitions des tables :

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
                        child_id bigint not null,
                        primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

For more examples and a complete explanation of a parent/child relationship mapping, see Chapitre 22, Exemple : père/fils for more information.

Des mappages d'association plus exotiques sont possibles, nous cataloguerons toutes les possibilités dans le prochain chapitre.