Chapitre 6. Mapping des Collections

6.1. Collections persistantes

Cette section ne contient pas beaucoup d'exemples Java. Nous supposons que vous savez déjà utiliser le framework de collections Java. Il n'y a donc pas grand chose de plus à savoir - avec quelques définitions, vous pouvez utiliser les collections Java de la même manière que vous l'avez toujours fait.

Hibernate peut persister des instances de java.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List, et tous les tableaux d'entités et valeurs persistantes. Les propriétés de java.util.Collection ou java.util.List peuvent aussi être persistées avec la sémantique de sac (bag).

A savoir: les collections persistantes ne conservent pas de sémantique supplémentaire introduite par les implémentations de l'interface Collection (ex: l'ordre d'itération d'une LinkedHashSet). Les collections persistantes agissent respectivement comme HashMap, HashSet, TreeMap, TreeSet et ArrayList Par ailleurs, le type java de la propriété contenant la collection doit être du type de l'interface (ex: Map, Set ou List ; jamais HashMap, TreeSet ou ArrayList). Cette restriction existe parce qu'Hibernate remplace dasn votre dos vos instances de Map, Set et List par des instances de ses propres implémentations de Map, Set ou List (A ce titre, faîtes attention à l'utilisation de == sur vos collections).

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.save(cat);
kittens = cat.getKittens(); //Okay, la collection kittens est un Set
(HashSet) cat.getKittens(); //Erreur !

Les collections obéissent aux règles auxquelles sont soumises les types valeurs : pas de références partagées, les collections sont créées ou effacées en même temps que l'entité contenante. A cause de la nature du modèle relationnel, elles ne supportent pas la sémantique nulle; Hibernate ne distingue pas une collection nulle d'une collection vide.

Les collections sont automatiquement persistées lorsqu'elles sont référencées par un objet persistant et automatiquement effacées lorsqu'elles sont déréférencées. Si une collection est passée d'un objet persistant à un autre, ses éléments devrait être déplacés d'une table vers une autre. Vous ne devriez pas vous soucier beaucoup de cela. Vous n'avez qu'à utiliser les collections Hibernate de la même façon que les collections Java ordinaires, mais vous devez être certains de comprendre les définitions des associations bidirectionnelles (discutées plus tard) avant de les utiliser.

Les instances de collections se différencient en base de données par une clé étrangère vers l'entité contenante. Cette clé étrangère est appelée clé de collection. La clé de collection est mappée par l'élément <key>.

Les collections peuvent contenir d'autres types que ceux d'Hibernate, y compris tous les types de base, les types entités et les composants. Ceci est une définition importante : un objet dans une collection peut être traité soit avec la sémantique d'un "passage par valeur" (elle dépendra alors du propriétaire de la collection) soit être une référence à une autre entité ayant son propre cycle de vie. Les collections ne peuvent contenir d'autres collections. Le type contenu est appelé type d'élément de collection. Les éléments de collection sont mappés grâce à <element>, <composite-element>, <one-to-many>, <many-to-many> ou <many-to-any>. Les deux premiers mappent des éléments avec la sémantique de valeur, les trois autres sont utilisés pour mapper des associations avec des entités.

Toutes les collections, à l'exception de Set et Bag ont une colonne index - une colonne qui mappe vers l'index d'un tableau, d'une List ou une clé de Map. L'index de Map peut être de n'importe quel type de base, type entité ou même type composite (il ne peut être une collection). L'index d'un tableau ou d'une list est toujours de type integer. Les index sont mappés en utilisant <index>, <index-many-to-many>, <composite-index> ou <index-many-to-any>.

Il existe beaucoup de mappings différents pour les collections, couvrant plusieurs modèles relationnels. Nous vous conseillons d'essayer l'outil de génération de schéma pour assimiler comment ces déclarations se traduissent en base de données.

6.2. Mapper une Collection

Les collections sont mappées par les éléments <set>, <list>, <map>, <bag>, <array> et <primitive-array>. <map> est représentatif :

<map
    name="nomDePropriete"                                       (1)
    table="nom_de_table"                                        (2)
    schema="nom_de_schema"                                      (3)
    lazy="true|false"                                           (4)
    inverse="true|false"                                        (5)
    cascade="all|none|save-update|delete|all-delete-orphan"     (6)
    sort="unsorted|natural|ClassDeComparateur"                  (7)
    order-by="nom_de_colonne asc|desc"                          (8)
    where="clause SQL where quelconque"                         (9)
    outer-join="true|false|auto"                                (10)
    batch-size="N"                                              (11)
    access="field|property|NomDeClasse"                         (12)
>

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

name : le nom de la prorpié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 = false) : active l'initialisation tardive (non utilisé pour les tableaux)

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

outer-join (optionnel) : spécifie que la collection doit être chargée en utilisant une jointure ouverte, lorsque c'est possible. Seule une collection (par SELECT SQL) pour être chargée avec une jointure ouverte.

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

Le mapping d'une List ou d'un tableau nécessite une colonne à part pour contenir l'index du tableau ou de la list (le i dans foo[i]). Si votre modèle relationnel n'a pas de colonne index, (par exemple si vous travaillez avec une base de données sur laquelle vous n'avez pas la main), utilisez alors un Set non ordonné. Cela semble aller à l'encontre de beaucoup de personnes qui pensent qu'une List est un moyen pratique d'accéder à une collection désordonnée. Les collections Hibernate obéissent strictement aux sémantiques des interfaces des collections Set, List et Map. Les éléments de List ne se réarrangent pas spontanément !

D'un autre côté, les personnes qui veulent utiliser une List pour émuler le comportement d'un bag (sac) ont une raison légitime. Un bag (sac) est une collection non triée, non ordonnée qui peut contenir le même élément plusieurs fois. Le framework de collections Java ne dispose pas d'une interface Bag, ainsi vous devez l'émuler avec une List. Hibernate vous permet de mapper des propriétés de type List ou Collection avec l'élément <bag>. Notez que la définition de bag ne fait pas partie du contrat Collection et qu'elle est même en conflit avec certains aspects de la définition du contrat d'une List (vous pouvez, cependant, trier un bag (sac) de manière aritraire, nous en discuterons plus tard).

Note : Les bags Hibernate volumineux mappé avec inverse="false" ne sont pas efficaces et doivent être évités ; Hibernate ne peut créer, effacer ou mettre à jour individuellement les enregistrements, puisqu'il n'y a pas de clé pouvant servir à identifier un enregistrement particulier.

6.3. Collections de valeurs et associations Plusieurs-vers-Plusieurs

Une table de collection est requise pour toute collection de valeurs et toute collection de références vers d'autres entités mappées avec une association plusieurs-vers-plusieurs (la définition naturelle d'une collection Java). La table a besoin de d'une(de) clé(s) étrangère(s), d'une(de) colonne(s) élément et si possible d'une(de) colonne(s) index.

La clé étrangère d'une table de collection vers la table de l'entité propriétaire est déclarée en utilisant l'élément <key>.

<key column="nom_de_colonne"/>
(1)

column (requis) : Le nom de la colonne clé étrangère

Pour les collections indexées comme les lists et les maps, nous avons besoin d'un élément <index>. Pour les lists, cette colonne contient des entiers numérotés à partir de zéro. Soyez certains que votre index commence bien par zéro (surtout si vous travaillez avec une base de données existante). Pour les maps, la colonne peut contenir des valeurs de chacun des types Hibernate.

<index
        column="nom_de_colonne"             (1)
        type="nomdetype"                    (2)
/>
(1)

column (requis) : Le nom de la colonne contenant les valeurs de l'index de la collection.

(2)

type (optionnel, par défaut = integer) : Le type de l'index de la collection.

Alternativement, une map peut être indexée par des objets de type entité. Nous utilisons alors l'élément <index-many-to-many>.

<index-many-to-many
        column="nom_de_colonne"             (1)
        class="NomDeClasse"                 (2)
/>
(1)

column (requis): Le nom de la colonne contenant la clé étrangère vers l'entité index de la collection.

(2)

class (requis) : La classe entité utilisée comme index de collection.

Pour une collection de valeurs, nous utilisons l'element <element>.

<element
        column="nom_de_colonne"             (1)
        type="nomdetype"                    (2)
/>
(1)

column (requis) : Le nom de la colonne contenant les valeurs des éléments de la collection.

(2)

type (requis) : Le type d'un élément de la colleciton.

Une collection d'entités avec sa propre table correspond à la notion relationnelle d'une association plusieurs-vers-plusieurs. Une association plusieurs vers plusieurs est le mapping le plus naturel pour une collection Java mais n'est généralement pas le meilleur modèle relationnel.

<many-to-many
        column="nom_de_colonne"                            (1)
        class="NomDeClasse"                                (2)
        outer-join="true|false|auto"                       (3)
    />
(1)

column (requis) : Le nom de la colonne contenant la clé étrangère de l'entité

(2)

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

(3)

outer-join (optionnel - par défaut = auto) : active le chargement par jointure ouverte pour cette association lorsque hibernate.use_outer_join est activé.

Quelques exemple, d'abord un set de String :

<set name="names" table="NAMES">
    <key column="GROUPID"/>
    <element column="NAME" type="string"/>
</set>

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

<bag name="sizes" table="SIZES" order-by="SIZE ASC">
    <key column="OWNER"/>
    <element column="SIZE" type="integer"/>
</bag>

Un tableau d'entités - dans ce cas une association many to many (notez que les entités ont un cycle de vie, cascade="all"):

<array name="foos" table="BAR_FOOS" cascade="all">
    <key column="BAR_ID"/>
    <index column="I"/>
    <many-to-many column="FOO_ID" class="org.hibernate.Foo"/>
</array>

Une map d'index de String vers des Date:

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

Une List de composants (décrits dans le prochain chapitre):

<list name="carComponents" table="car_components">
    <key column="car_id"/>
    <index column="posn"/>
    <composite-element class="org.hibernate.car.CarComponent">
            <property name="price" type="float"/>
            <property name="type" type="org.hibernate.car.ComponentType"/>
            <property name="serialNumber" column="serial_no" type="string"/>
    </composite-element>
</list>

6.4. Associations Un-vers-Plusieurs

Une association un vers plusieurs lie les tables de deux classes directement, sans table de collection intermédiaire (Ceci implémente un modèle relationnel un-vers-plusieurs). Ce modèle relationnel perd quelques unes des sémantiques des collections Java:

  • Il ne peut y avoir de valeur nulle contenue dans map, set ou list

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

  • Une instance de la classe entité contenue ne peut apparaitre dans plus d'une valeur de l'index de la collection

Une association de Foo vers Bar nécessite l'ajout d'une colonne clé et si possible d'une colonne index vers la table de la classe entité contenue, Bar. Ces colonnes sont mappées en utilisant les éléments <key> et <index> décrits précédemment.

Le tag <one-to-many> indique une assocation un vers plusieurs.

<one-to-many class="NomDeClasse"/>
(1)

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

Exemple :

<set name="bars">
    <key column="foo_id"/>
    <one-to-many class="org.hibernate.Bar"/>
</set>

Notez que l'élément <one-to-many> n'a pas besoin de déclarer de colonne. Il n'est pas non plus nécessaire de déclarer un nom de table ou quoique ce soit.

Note importante : Si la colonne <key> d'une association <one-to-many> est déclarée NOT NULL, Hibernate peut provoquer des violations de contraintes lorsqu'il créé ou met à jour l'association. Pour éviter ce problème, vous devez utiliser une association bidirectionnelle avec l'extrémité plusieurs (set ou bag) marquéz comme inverse="true". Voir la discussion sur les associations bidirectionnelles plus tard.

6.5. Initialisation tardive

Les collections (autres que les tableaux) peuvent être initialisée de manière tardives, ce qui signifie qu'elles ne chargent leur état de la base de données que lorsque l'application a besoin d'y accéder. L'initialisation intervient de manière transparente pour l'utilisateur, l'application n'a donc pas à se soucier de cela (en fait, l'initialisation transparente est la principale raison pour laquelle Hibernate a besoin de ses propres implémentations de collection). Ainsi, si l'application essaie quelque chose comme :

s = sessions.openSession();
User u = (User) s.find("from User u where u.name=?", userName, Hibernate.STRING).get(0);
Map permissions = u.getPermissions();
s.connection().commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // Erreur !

Il arrivera une mauvaise surprise. Dans la mesure où les collections "permissions" n'ont pas été initialisées avant que la Session soit commitée, la collection ne sera jamais capable de charger son état. Pour corriger le cas précédent, il faut déplacer la ligne qui lit la collection juste avant le commit (Il existe d'autres moyens avancés de résourdre ce problème).

Une autre façon de faire est d'utilisez une collection initialisée immédiatement. Puisque l'initialisation tardive peut mener à des bogues comme le précédent, l'initialisation immédiate est le comportement par défaut. Cependant, il est préférable d'utiliser l'initialisation tardive pour la plupart des collections, spécialement pour les collections d'entités (pour des raisons de performances).

Les exceptions qui arrivent lors d'une initialisation tardive sont encapsulées dans une LazyInitializationException.

Déclarer une collection comme tardive en utilisant l'attribut optionnel lazy :

<set name="names" table="NAMES" lazy="true">
    <key column="group_id"/>
    <element column="NAME" type="string"/>
</set>

Dans certaines architectures applicatives, particulièrement quand le code qui accède aux données et celui qui les utilise ne se trouvent pas dans la même couche, on peut avoir un problème pour garantir que la session est ouverte pour l'initialisation de la collection. Il y a deux moyens clzssiques de résoudre ce problème :

  • Dans une application web, un filtre de servlet peut être utilisé pour ne fermer la Session qu'à la fin de la requête de l'utilisateur, une fois que la vue a été rendue. Bien entendu, cela nécessite de mettre en place une gestion rigoureuse des exceptions de l'infrastructure applicative. Il est vital que la Session soit fermée et la transaction achevée avant le retour vers l'utilisateur, même si une exception survient pendant le rendement de la vue. Le filtre de servlet doit pouvoir accéder à la Session pour cette approche. Nous recommandons d'utiliser une variable ThreadLocal pour garder la Session courante (voir chapitre 1, pour un exemple d'implémentation).Section 1.4, « Jouer avec les chats »).

  • Dans une application avec une couche métier séparée, la logique métier doit "préparer" toutes les collections qui seront requises par la couche web avant d'effectuer le retour. Cela signifie que la couce métier doit charger toutes les données nacessaires au cas d'utilisation qui nous occupe et les retourner à la couche de présentation/web. Généralement, l'application invoque Hibernate.initialize() pour chaque collection qui sera requise par l'étage web (cet appel doit être effectué avant la fermeture de la session) ou charg la collection via une requête en utilisant une clause FETCH.

  • Vous pouvez aussi attacher un objet précédemment chargé à une nouvelle Session en utilisant update() ou lock() avant d'accéder aux collections non initialisées (ou autres proxys). Hibernate ne peut le faire automatiquement, cela introduirait une sémantique de transaction !

Vous pouvez utiliser la méthode filter() de l'API Session d'Hibernate pour avoir la taille de la collection sans l'initialiser :

( (Integer) s.filter( collection, "select count(*)" ).get(0) ).intValue()

filter() ou createFilter() sont aussi utilisés pour récupérer de manière efficace un sous ensemble d'une collection sans avoir à l'initialiser entièrement.

6.6. Collections triées

Hibernate supporte les collections qui implémentent java.util.SortedMap et java.util.SortedSet. Vous devez spécifier un comparateur dans le fichier de mapping :

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

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

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

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

Si vous souhaitez que la base de données trie elle même les éléments d'une collection, utilisez l'attribut order-by des mappings de set, bag ou map. Cette solution n'est disponible qu'à partir du JDK 1.4 ou plus (elle est implémentée via les LinkedHashSet ou LinkedHashMap). Ceci effectue un tri dans la requête SQL, et non en mémoire dans la JVM.

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

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

Notez que la valeur de l'attribut order-by est un tri SQL et non HQL !

Les associations peuvent aussi être triées à l'exécution par des critères arbitraires en utilisant filter().

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

6.7. Utiliser un <idbag>

Si, comme nous, vous êtes complêtement d'accord sur le fait que les clés composites sont une mauvaise idée et que les entités devraient avoir des identifiants synthétiques (clés techniques), alors vous devez trouver étrange que les associations plusieurs vers plusieurs et les collections de valeurs que nous avons montrées jusqu'à présent soient toutes mappées dans des tables possédant des clés composites ! En fait, ce point est discutable ; une table d'association pure ne semble pas tirer bénéfice d'une clé technique (bien qu'une collection de valeurs composées le pourrait). Néanmoins, Hibernate propose une fonctionnalité (un peu expérimentale) qui vous permet de mapper des associations many to many et des collections de valeurs vers une table ayant une clé technique.

L'élément <idbag> vous permet de mapper une List (ou Collection) avec les caractéristiques d'un bag.

<idbag name="lovers" table="LOVERS" lazy="true">
    <collection-id column="ID" type="long">
        <generator class="hilo"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
</idbag>

Comme vous pouvez le voir, un <idbag> possède un générateur d'id synthétique, tout comme une classe entité ! Une clé technique différente est assignée à chaque enregistrement de la collection. Hibernate ne fournit cependant pas de mécanisme pour trouver la valeur de la clé technique d'un enregistrement particulier.

Notez que la performance de mise à jour pour un <idbag> est nettement meilleure que pour un <bag> ! Hibernate peut localiser les enregistrements individuellement et les mettre à jour ou les effacer individuellement, comme dans une list, une map ou un set.

Dans l'implémentation courante, la génération d'identifiant identity n'est pas supportée pour les identifiants de collection <idbag>.

6.8. Associations Bidirectionnelles

Une association bidirectionnelle permet de naviguer à partir des deux extrémités de l'association. Les deux types d'association bidirectionnelles supportées sont :

un-vers-plusieurs

un set ou un bag d'un côté, un simple entité de l'autre

plusieurs-vers-plusieurs

un set ou un bag de chaque côté

Notez qu'Hibernate ne supporte pas les associations bidirectionnelles avec une collection indexée (list, map or array), vous devez utiliser un mapping set ou bag.

Vous pouvez spécifier une association plusieurs-vers-plusieurs bidirectionnelle, en mappant simplement deux associations plusieurs-vers-plusieurs à la même table d'association de la base de données et en déclarant une extrémité inverse (celle de votre choix). Voici un exemple d'association bidirectionnelle d'une classe vers elle-même (chaque categorie peut avoir plusieurs items et chaque item peut être dans plusieurs categories):

<class name="org.hibernate.auction.Category">
    <id name="id" column="ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM" lazy="true">
        <key column="CATEGORY_ID"/>
        <many-to-many class="org.hibernate.auction.Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="org.hibernate.auction.Item">
    <id name="id" column="ID"/>
    ...

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

Les changement effectués uniquement sur l'extrêmité inverse ne sont pas persistés. Ceci signifie qu'Hibernate possède deux représentations en mémoire pour chaque association bidirectionnelle, un lien de A vers B et l'autre de B vers A. Ceci est plus facile à comprendre si vous penser au modèle objet Java et comment l'on créé une relation plusieurs-vers-plusieurs en Java:

category.getItems().add(item);          // La catégorie connait désormais la relation
item.getCategories().add(category);     // L'Item connait désormais la relation

session.update(item);                     // Aucun effet, rien n'est persisté !
session.update(category);                 // La relation est persistée

Le côté non-inverse est utilisé pour sauvegarder la réprésentation mémoire de la relation en base de données. Nous aurions un INSERT/UPDATE inutile et provoquerions probalement une violation de contrainte de clé étrangère si les deux côtés déclenchaient la mise à jour ! Ceci est également vrai pour les associations un-vers-plusieurs bidirectionnelles.

Vous pouvez mapper une association un-vers-plusieurs bidirectionnelle en mappant une association un-vers-plusieurs vers la(les) même(s) colonne(s) de table que sa relation inverse plusieurs-vers-une et en déclarant l'extrêmité plisieurs avec inverse="true".

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

<class name="eg.Child">
    <id name="id" column="id"/>
    ....
    <many-to-one name="parent" class="eg.Parent" column="parent_id"/>
</class>

Mapper un côté d'une association avec inverse="true" n'impacte pas les opérations de cascade, ce sont deux concepts différents !

6.9. Associations ternaires

Il y a deux approches pour mapper une association ternaire. La première est d'utiliser des éléments composites (voir ci-dessous). La seconde est d'utiliser une Map ayant une association comme index :

<map name="contracts" lazy="true">
    <key column="employer_id"/>
    <index-many-to-many column="employee_id" class="Employee"/>
    <one-to-many column="contract_id" class="Contract"/>
</map>
<map name="connections" lazy="true">
    <key column="node1_id"/>
    <index-many-to-many column="node2_id" class="Node"/>
    <many-to-many column="connection_id" class="Connection"/>
</map>

6.10. Associations hétérogènes

Les éléments <many-to-any> et <index-many-to-any> fournissent de vraies associations hétérogènes. Ces éléments de mapping fonctionnnent comme l'élément <any> - et ne devraient être utilisés que très rarement.

6.11. Exemples de collection

Les sections précédentes sont un peu confuses. Regardons un exemple, cette classe :

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

    ....
    ....
}

possède une collection d'instances de eg.Child. Si chacun des child (fils) possède au plus un parent, le mapping le plus naturel est une association un-vers-plisieurs :

<hibernate-mapping>

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

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

</hibernate-mapping>

Ceci mappe les définitions 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 un-vers-plusieurs bidirectionnelle :

<hibernate-mapping>

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

    <class name="eg.Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="eg.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

D'un autre côté, si le child (fils) peut avoir plusieurs parents, une association plusieurs-vers-plusieurs est appropriée :

<hibernate-mapping>

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

    <class name="eg.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