372 lines
15 KiB
XML
372 lines
15 KiB
XML
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<chapter id="example-parentchild">
|
|
<title>Exemple : Père/Fils</title>
|
|
|
|
<para>
|
|
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 <literal>Père</literal>
|
|
et <literal>Fils</literal> comme des classes entités liées par une association <literal><one-to-many></literal> du
|
|
<literal>Père</literal> vers le <literal>Fils</literal> (l'autre approche est de déclarer le <literal>Fils</literal>
|
|
comme un <literal><composite-element></literal>). Il est évident que le sens de l'association un vers plusieurs
|
|
(dans Hibernate) est bien moins proche du sens habituel d'une relation père/fils que ne l'est celui d'un
|
|
élément cmposite. Nous allons vous expliquer comment utiliser une association <emphasis>un vers plusieurs bidirectionnelle
|
|
avec cascade</emphasis> afin de modéliser efficacement et élégamment une relation père/fils, ce n'est vraiment
|
|
pas difficile !
|
|
</para>
|
|
|
|
<sect1 id="example-parentchild-collections">
|
|
<title>Une note à propos des collections</title>
|
|
|
|
<para>
|
|
Les collections Hibernate sont considérées comme étant une partie logique
|
|
de l'entité dans laquelle elles sont contenues ; jamais des entités qu'elle
|
|
contient. C'est une distinction crutiale ! Les conséquences sont les suivantes :
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
Quand nous ajoutons / retirons un objet d'une collection, le numéro de version du
|
|
propriétaire de la collection est incrémenté.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Si un objet qui a été enlevé d'une collection est une instance de type valeur (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
|
|
aura pour conséquence que son état sera immédiatement persistant.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Si une entité est enlevée d'une collection (association un-vers-plusieurs
|
|
ou plusieurs-vers-plusieurs), par défaut, elle ne sera pas effacée. Ce comportement
|
|
est complètement logique - une modification de l'un des états internes d'une 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é.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Le comportement par défaut est donc que l'ajout d'une entité dans une collection créé
|
|
simplement le lien entre les deux entités, et 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.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-bidir">
|
|
<title>un-vers-plusieurs bidirectionnel</title>
|
|
|
|
<para>
|
|
Supposons que nous ayons une simple association <literal><one-to-many></literal>
|
|
de <literal>Parent</literal> vers <literal>Child</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Si nous executions le code suivant
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = .....;
|
|
Child c = new Child();
|
|
p.getChildren().add(c);
|
|
session.save(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Hibernate exécuterait deux ordres SQL:
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>un <literal>INSERT</literal> pour créer l'enregistrement pour <literal>c</literal></para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
un <literal>UPDATE</literal> pour créer le lien de <literal>p</literal> vers
|
|
<literal>c</literal>
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Ceci est non seuleument inefficace, mais viole aussi toute contrainte <literal>NOT NULL</literal> sur
|
|
la colonne <literal>parent_id</literal>. Nous pouvons réparer la contrainte de nullité
|
|
en spécifiant <literal>not-null="true"</literal> dans le mapping de la collection :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children">
|
|
<key column="parent_id" not-null="true"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Cependant ce n'est pas la solution recommandée.
|
|
</para>
|
|
|
|
<para>
|
|
La cause sous jacente à ce comportement est que le lien (la clé étrangère <literal>parent_id</literal>) de
|
|
<literal>p</literal> vers <literal>c</literal> n'est pas considérée comme faisant partie de l'état
|
|
de l'objet <literal>Child</literal> et n'est donc pas créé par l'<literal>INSERT</literal>.
|
|
La solution est donc que ce lien fasse partie du mapping de <literal>Child</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<many-to-one name="parent" column="parent_id" not-null="true"/>]]></programlisting>
|
|
|
|
<para>
|
|
(Nous avons aussi besoin d'ajouter la propriété <literal>parent</literal> dans la classe <literal>Child</literal>).
|
|
</para>
|
|
|
|
<para>
|
|
Maintenant que l'état du lien est géré par l'entité <literal>Child</literal>, nous spécifions à la
|
|
collection de ne pas mettre à jour le lien. Nous utilisons l'attribut <literal>inverse</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children" inverse="true">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Le code suivant serait utilisé pour ajouter un nouveau <literal>Child</literal>
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = new Child();
|
|
c.setParent(p);
|
|
p.getChildren().add(c);
|
|
session.save(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Maintenant, seul un <literal>INSERT</literal> SQL est nécessaire !
|
|
</para>
|
|
|
|
<para>
|
|
Pour alléger encore un peu les choses, nous devrions créer une méthode <literal>addChild()</literal>
|
|
dans <literal>Parent</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public void addChild(Child c) {
|
|
c.setParent(this);
|
|
children.add(c);
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Le code d'ajout d'un <literal>Child</literal> serait alors
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = new Child();
|
|
p.addChild(c);
|
|
session.save(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-cascades">
|
|
<title>Cycle de vie en cascade</title>
|
|
|
|
<para>
|
|
L'appel explicite de <literal>save()</literal> est un peu fastidieux. Nous pouvons
|
|
simplifier cela en utilisant les cascades.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Simplifie le code précédent en
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = new Child();
|
|
p.addChild(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
De la même manière, nous n'avons pas à itérer sur les fils lorsque nous sauvons
|
|
ou effacons un <literal>Parent</literal>. Le code suivant efface <literal>p</literal>
|
|
et tous ses fils de la base de données.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
session.delete(p);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Par contre, ce code
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = (Child) p.getChildren().iterator().next();
|
|
p.getChildren().remove(c);
|
|
c.setParent(null);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
n'effacera pas <literal>c</literal> de la base de données, il enlèvera seulement
|
|
le lien vers <literal>p</literal> (et causera une violation de contrainte
|
|
<literal>NOT NULL</literal>, dans ce cas).
|
|
Vous devez explicitement utiliser <literal>delete()</literal> sur <literal>Child</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Parent p = (Parent) session.load(Parent.class, pid);
|
|
Child c = (Child) p.getChildren().iterator().next();
|
|
p.getChildren().remove(c);
|
|
session.delete(c);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
Dans notre cas, un <literal>Child</literal> ne peut pas vraiment exister sans son père. Si nous
|
|
effacons un <literal>Child</literal> de la collection, nous voulons vraiment qu'il soit effacé.
|
|
Pour cela, nous devons utiliser <literal>cascade="all-delete-orphan"</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="children" inverse="true" cascade="all-delete-orphan">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
A noter : même si le mapping de la collection spécifie <literal>inverse="true"</literal>, 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 colleciton.
|
|
Il ne suffit pas d'appeler explicitement <literal>setParent()</literal>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-update">
|
|
<title>Cascades et <literal>unsaved-value</literal></title>
|
|
|
|
<para>
|
|
Supposons que nous ayons chargé un <literal>Parent</literal> dans une <literal>Session</literal>,
|
|
que nous l'ayons ensuite modifié et que voulions persiter ces modifications dans une nouvelle session
|
|
en appelant <literal>update()</literal>.
|
|
Le <literal>Parent</literal> contiendra une collection de fils et, puisque la cascade est activée,
|
|
Hibernate a besoin de savoir quels fils viennent d'être instanciés et quels fils proviennent de la base
|
|
de données. Supposons aussi que <literal>Parent</literal> et <literal>Child</literal> ont tous deux
|
|
des identifiants du type <literal>Long</literal>.
|
|
Hibernate utilisera la propriété de l'identifiant et la propriété de la version/horodatage pour déterminer quels fils sont nouveaux
|
|
(vous pouvez aussi utiliser la propriété version ou timestamp, voir
|
|
<xref linkend="manipulatingdata-updating-detached"/>).
|
|
<emphasis>Dans Hibernate3, il n'est plus nécessaire de spécifier
|
|
une <literal>unsaved-value</literal> explicitement.</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
Le code suivant mettra à jour <literal>parent</literal> et <literal>child</literal>
|
|
et insérera <literal>newChild</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[//parent et child ont été chargés dans une session précédente
|
|
parent.addChild(child);
|
|
Child newChild = new Child();
|
|
parent.addChild(newChild);
|
|
session.update(parent);
|
|
session.flush();]]></programlisting>
|
|
|
|
<para>
|
|
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,
|
|
puisqu'Hibernate ne peut pas utiliser la propriété de l'identifiant pour distinguer un objet
|
|
nouvellement instancié (avec un identifiant assigné par l'utilisateur) d'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.
|
|
</para>
|
|
|
|
<!-- undocumenting
|
|
<para>
|
|
There is one further possibility. The <literal>Interceptor</literal> method named
|
|
<literal>isUnsaved()</literal> lets the application implement its own strategy for distinguishing
|
|
newly instantiated objects. For example, you could define a base class for your persistent classes.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Persistent {
|
|
private boolean _saved = false;
|
|
public void onSave() {
|
|
_saved=true;
|
|
}
|
|
public void onLoad() {
|
|
_saved=true;
|
|
}
|
|
......
|
|
public boolean isSaved() {
|
|
return _saved;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
(The <literal>saved</literal> property is non-persistent.)
|
|
Now implement <literal>isUnsaved()</literal>, along with <literal>onLoad()</literal>
|
|
and <literal>onSave()</literal> as follows.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public Boolean isUnsaved(Object entity) {
|
|
if (entity instanceof Persistent) {
|
|
return new Boolean( !( (Persistent) entity ).isSaved() );
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public boolean onLoad(Object entity,
|
|
Serializable id,
|
|
Object[] state,
|
|
String[] propertyNames,
|
|
Type[] types) {
|
|
|
|
if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
|
|
return false;
|
|
}
|
|
|
|
public boolean onSave(Object entity,
|
|
Serializable id,
|
|
Object[] state,
|
|
String[] propertyNames,
|
|
Type[] types) {
|
|
|
|
if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
|
|
return false;
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Don't worry; in Hibernate3 you don't need to write any of this kind of code if you don't want to.
|
|
</para>
|
|
-->
|
|
</sect1>
|
|
|
|
<sect1 id="example-parentchild-conclusion">
|
|
<title>Conclusion</title>
|
|
|
|
<para>
|
|
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 pattern père / fils.
|
|
</para>
|
|
|
|
<para>
|
|
Nous avons évoqué une alternative dans le premier paragraphe. Aucun des points traités précédemment n'existe
|
|
dans le cas d'un mapping <literal><composite-element></literal> qui possède exactement la sémantique
|
|
d'une relation père / fils. Malheureusement, il y a deux grandes limitations pour les classes é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.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter> |