Hibernate.org Community Documentation

- Précédent
- Suivant

## Chapitre 24. Exemple : père/fils

L'une des premières choses que les nouveaux utilisateurs essaient de faire avec Hibernate est de modéliser une relation père/fils. Il y a deux approches différentes pour cela. Pour un certain nombre de raisons, la méthode la plus courante, en particulier pour les nouveaux utilisateurs, est de modéliser les deux relations `Père` et `Fils` comme des classes entités liées par une association `<one-to-many>` du `Père` vers le `Fils` (l'autre approche est de déclarer le `Fils` comme un `<composite-element>`). On constate que la sémantique par défaut de l'association un-à-plusieurs (dans Hibernate) est bien moins proche du sens habituel d'une relation père/fils que celle d'un mappage d'élément composite. Nous allons vous expliquer comment utiliser une association un-à-plusieurs bidirectionnelle avec cascade afin de modéliser efficacement et élégamment une relation père/fils.

## 24.1. Une note à propos des collections

Les collections Hibernate sont considérées comme étant une partie logique de leur entité propriétaire, jamais des entités qu'elle contient. C'est une distinction cruciale ! Les conséquences sont les suivantes :

- Quand nous ajoutons / retirons un objet d'une collection, le numéro de version du propriétaire de la collection est incrémenté.

- Si un objet qui a été enlevé d'une collection est une instance de type valeur (par ex : élément composite), cet objet cessera d'être persistant et son état sera complètement effacé de la base de données. Par ailleurs, ajouter une instance de type valeur dans une collection entraînera que son état sera immédiatement persistant.

- Si une entité est enlevée d'une collection (association un-à-plusieurs ou plusieurs-à-plusieurs), elle ne sera pas effacée par défaut. Ce comportement est complètement logique - une modification de l'un des états internes d'une autre entité ne doit pas causer la disparition de l'entité associée. De même, l'ajout d'une entité dans une collection n'engendre pas, par défaut, la persistance de cette entité.

Le comportement par défaut est donc que l'ajout d'une entité dans une collection crée simplement le lien entre les deux entités, alors qu'effacer une entité supprime ce lien. C'est le comportement le plus approprié dans la plupart des cas. Ce comportement n'est cependant pas approprié lorsque la vie du fils est liée au cycle de vie du père.

## 24.2. Un-à-plusieurs bidirectionnel

Supposons que nous ayons une simple association `<one-to-many>` de `Parent` à `Child`.

    <set name="children">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

Si nous exécutions le code suivant :

    Parent p = .....;

    Child c = new Child();

    p.getChildren().add(c);

    session.save(c);

    session.flush();

Hibernate exécuterait deux ordres SQL :

- un `INSERT` pour créer l'enregistrement pour `c`

- un `UPDATE` pour créer le lien de `p` vers `c`

Ceci est non seulement inefficace, mais viole aussi toute contrainte `NOT NULL` sur la colonne `parent_id`. Nous pouvons réparer la contrainte de nullité en spécifiant `not-null="true"` dans le mappage de la collection :

    <set name="children">

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

        <one-to-many class="Child"/>

    </set

    >

Cependant ce n'est pas la solution recommandée.

La cause sous jacente à ce comportement est que le lien (la clé étrangère `parent_id`) de `p` vers `c` n'est pas considérée comme faisant partie de l'état de l'objet `Child` et n'est donc pas créé par l'`INSERT`. La solution est donc que ce lien fasse partie du mappage de `Child`.

    <many-to-one name="parent" column="parent_id" not-null="true"/>

Nous avons aussi besoin d'ajouter la propriété `parent` dans la classe `Child`.

Maintenant que l'état du lien est géré par l'entité `Child`, nous spécifions à la collection de ne pas mettre à jour le lien. Nous utilisons l'attribut `inverse` pour faire cela :

    <set name="children" inverse="true">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

Le code suivant serait utilisé pour ajouter un nouveau `Child` :

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    c.setParent(p);

    p.getChildren().add(c);

    session.save(c);

    session.flush();

Maintenant, seul un SQL `INSERT` est nécessaire.

Pour alléger encore un peu les choses, nous créerons une méthode `addChild()` de `Parent`.

    public void addChild(Child c) {

        c.setParent(this);

        children.add(c);

    }

Le code d'ajout d'un `Child` serait alors :

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    p.addChild(c);

    session.save(c);

    session.flush();

## 24.3. Cycle de vie en cascade

L'appel explicite de `save()` est un peu fastidieux. Nous pouvons simplifier cela en utilisant les cascades.

    <set name="children" inverse="true" cascade="all">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

Cela simplifie le code précédent en :

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = new Child();

    p.addChild(c);

    session.flush();

De la même manière, nous n'avons pas à itérer sur les fils lorsque nous sauvons ou effaçons un `Parent`. Le code suivant efface `p` et tous ses fils de la base de données.

    Parent p = (Parent) session.load(Parent.class, pid);

    session.delete(p);

    session.flush();

Par contre, ce code :

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = (Child) p.getChildren().iterator().next();

    p.getChildren().remove(c);

    c.setParent(null);

    session.flush();

n'effacera pas `c` de la base de données, il enlèvera seulement le lien vers `p` (et causera une violation de contrainte `NOT NULL`, dans ce cas). Vous devez explicitement utiliser `delete()` sur `Child`.

    Parent p = (Parent) session.load(Parent.class, pid);

    Child c = (Child) p.getChildren().iterator().next();

    p.getChildren().remove(c);

    session.delete(c);

    session.flush();

Dans notre cas, un `Child` ne peut pas vraiment exister sans son père. Si nous effaçons un `Child` de la collection, nous voulons vraiment qu'il soit effacé. Pour cela, nous devons utiliser `cascade="all-delete-orphan"`.

    <set name="children" inverse="true" cascade="all-delete-orphan">

        <key column="parent_id"/>

        <one-to-many class="Child"/>

    </set

    >

À noter : même si le mappage de la collection spécifie `inverse="true"`, les cascades sont toujours assurées par l'itération sur les éléments de la collection. Donc, si vous avez besoin qu'un objet soit enregistré, effacé ou mis à jour par cascade, vous devez l'ajouter dans la collection. Il ne suffit pas d'appeler explicitement `setParent()`.

## 24.4. Cascades et `unsaved-value` (valeurs non sauvegardées)

Suppose we loaded up a `Parent` in one `Session`, made some changes in a UI action and wanted to persist these changes in a new session by calling `update()`. The `Parent` will contain a collection of children and, since the cascading update is enabled, Hibernate needs to know which children are newly instantiated and which represent existing rows in the database. We will also assume that both `Parent` and `Child` have generated identifier properties of type `Long`. Hibernate will use the identifier and version/timestamp property value to determine which of the children are new. (See Section 11.7, « Détection automatique d'un état ».) In Hibernate3, it is no longer necessary to specify an `unsaved-value` explicitly.

Le code suivant mettra à jour `parent` et `child` et insérera `newChild`.

    //parent and child were both loaded in a previous session

    parent.addChild(child);

    Child newChild = new Child();

    parent.addChild(newChild);

    session.update(parent);

    session.flush();

Ceci est très bien pour des identifiants générés, mais qu'en est-il des identifiants assignés et des identifiants composés ? C'est plus difficile, puisque Hibernate ne peut pas utiliser la propriété de l'identifiant pour distinguer entre un objet nouvellement instancié (avec un identifiant assigné par l'utilisateur) et un objet chargé dans une session précédente. Dans ce cas, Hibernate utilisera soit la propriété de version ou d'horodatage, soit effectuera vraiment une requête au cache de second niveau, soit, dans le pire des cas, à la base de données, pour voir si la ligne existe.

## 24.5. Conclusion

Il y a quelques principes à maîtriser dans ce chapitre et tout cela peut paraître déroutant la première fois. Cependant, dans la pratique, tout fonctionne parfaitement. La plupart des applications Hibernate utilisent le modèle père / fils.

Nous avons évoqué une alternative dans le premier paragraphe. Aucun des points traités précédemment n'existe dans le cas de mappings `<composite-element>` qui possède exactement la sémantique d'une relation père / fils. Malheureusement, il y a deux grandes limitations pour les classes d'éléments composites : les éléments composites ne peuvent contenir de collections, et ils ne peuvent être les fils d'entités autres que l'unique parent.

Copyright © 2004 Red Hat, Inc.

- PrécédentChapitre 23. Additional modules
- Niveau supérieur
- Sommaire
- SuivantChapitre 25. Exemple : application Weblog