hibernate-orm/doc/reference/fr/modules/example_parentchild.xml

372 lines
15 KiB
XML
Raw Normal View History

<?xml version="1.0" encoding="iso-8859-1"?>
<chapter id="example-parentchild">
<title>Exemple : P<>re/Fils</title>
<para>
L'une des premi<6D>res choses que les nouveaux utilisateurs essaient de faire avec Hibernate est de mod<6F>liser
une relation p<>re/fils. Il y a deux approches diff<66>rentes pour cela. Pour un certain nombre de raisons, la m<>thode la
plus courante, en particulier pour les nouveaux utilisateurs, est de mod<6F>liser les deux relations <literal>P<EFBFBD>re</literal>
et <literal>Fils</literal> comme des classes entit<69>s li<6C>es par une association <literal>&lt;one-to-many&gt;</literal> du
<literal>P<EFBFBD>re</literal> vers le <literal>Fils</literal> (l'autre approche est de d<>clarer le <literal>Fils</literal>
comme un <literal>&lt;composite-element&gt;</literal>). Il est <20>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
<20>l<EFBFBD>ment cmposite. Nous allons vous expliquer comment utiliser une association <emphasis>un vers plusieurs bidirectionnelle
avec cascade</emphasis> afin de mod<6F>liser efficacement et <20>l<EFBFBD>gamment une relation p<>re/fils, ce n'est vraiment
pas difficile !
</para>
<sect1 id="example-parentchild-collections">
<title>Une note <20> propos des collections</title>
<para>
Les collections Hibernate sont consid<69>r<EFBFBD>es comme <20>tant une partie logique
de l'entit<69> dans laquelle elles sont contenues ; jamais des entit<69>s qu'elle
contient. C'est une distinction crutiale ! Les cons<6E>quences sont les suivantes :
</para>
<itemizedlist>
<listitem>
<para>
Quand nous ajoutons / retirons un objet d'une collection, le num<75>ro de version du
propri<72>taire de la collection est incr<63>ment<6E>.
</para>
</listitem>
<listitem>
<para>
Si un objet qui a <20>t<EFBFBD> enlev<65> d'une collection est une instance de type valeur (ex :
<20>l<EFBFBD>ment composite), cet objet cessera d'<27>tre persistant et son <20>tat sera compl<70>tement effac<61>
de la base de donn<6E>es. Par ailleurs, ajouter une instance de type valeur dans une collection
aura pour cons<6E>quence que son <20>tat sera imm<6D>diatement persistant.
</para>
</listitem>
<listitem>
<para>
Si une entit<69> est enlev<65>e d'une collection (association un-vers-plusieurs
ou plusieurs-vers-plusieurs), par d<>faut, elle ne sera pas effac<61>e. Ce comportement
est compl<70>tement logique - une modification de l'un des <20>tats internes d'une entit<69>
ne doit pas causer la disparition de l'entit<69> associ<63>e !
De m<>me, l'ajout d'une entit<69> dans une collection n'engendre pas,
par d<>faut, la persistance de cette entit<69>.
</para>
</listitem>
</itemizedlist>
<para>
Le comportement par d<>faut est donc que l'ajout d'une entit<69> dans une collection cr<63><72>
simplement le lien entre les deux entit<69>s, et qu'effacer une entit<69> supprime ce lien.
C'est le comportement le plus appropri<72> dans la plupart des cas. Ce comportement n'est
cependant pas appropri<72> lorsque la vie du fils est li<6C>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>&lt;one-to-many&gt;</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<65>cuterait deux ordres SQL:
</para>
<itemizedlist>
<listitem>
<para>un <literal>INSERT</literal> pour cr<63>er l'enregistrement pour <literal>c</literal></para>
</listitem>
<listitem>
<para>
un <literal>UPDATE</literal> pour cr<63>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<69>
en sp<73>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<6E>e.
</para>
<para>
La cause sous jacente <20> ce comportement est que le lien (la cl<63> <20>trang<6E>re <literal>parent_id</literal>) de
<literal>p</literal> vers <literal>c</literal> n'est pas consid<69>r<EFBFBD>e comme faisant partie de l'<27>tat
de l'objet <literal>Child</literal> et n'est donc pas cr<63><72> 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<72>t<EFBFBD> <literal>parent</literal> dans la classe <literal>Child</literal>).
</para>
<para>
Maintenant que l'<27>tat du lien est g<>r<EFBFBD> par l'entit<69> <literal>Child</literal>, nous sp<73>cifions <20> la
collection de ne pas mettre <20> 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<69> 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<6C>ger encore un peu les choses, nous devrions cr<63>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<70>c<EFBFBD>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<6E>re, nous n'avons pas <20> it<69>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<6E>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<6E>es, il enl<6E>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<61>.
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<73>cifie <literal>inverse="true"</literal>, les cascades
sont toujours assur<75>es par l'it<69>ration sur les <20>l<EFBFBD>ments de la collection. Donc, si vous avez besoin
qu'un objet soit enregistr<74>, effac<61> ou mis <20> 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<72> un <literal>Parent</literal> dans une <literal>Session</literal>,
que nous l'ayons ensuite modifi<66> 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<69>e,
Hibernate a besoin de savoir quels fils viennent d'<27>tre instanci<63>s et quels fils proviennent de la base
de donn<6E>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<72>t<EFBFBD> de l'identifiant et la propri<72>t<EFBFBD> de la version/horodatage pour d<>terminer quels fils sont nouveaux
(vous pouvez aussi utiliser la propri<72>t<EFBFBD> version ou timestamp, voir
<xref linkend="manipulatingdata-updating-detached"/>).
<emphasis>Dans Hibernate3, il n'est plus n<>cessaire de sp<73>cifier
une <literal>unsaved-value</literal> explicitement.</emphasis>
</para>
<para>
Le code suivant mettra <20> jour <literal>parent</literal> et <literal>child</literal>
et ins<6E>rera <literal>newChild</literal>.
</para>
<programlisting><![CDATA[//parent et child ont <20>t<EFBFBD> charg<72>s dans une session pr<70>c<EFBFBD>dente
parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();]]></programlisting>
<para>
Ceci est tr<74>s bien pour des identifiants g<>n<EFBFBD>r<EFBFBD>s, mais qu'en est-il des identifiants assign<67>s et des
identifiants compos<6F>s ? C'est plus difficile,
puisqu'Hibernate ne peut pas utiliser la propri<72>t<EFBFBD> de l'identifiant pour distinguer un objet
nouvellement instanci<63> (avec un identifiant assign<67> par l'utilisateur) d'un objet charg<72> dans une session pr<70>c<EFBFBD>dente.
Dans ce cas, Hibernate utilisera soit la propri<72>t<EFBFBD> de version ou d'horodatage, soit effectuera vraiment une requ<71>te au cache
de second niveau, soit, dans le pire des cas, <20> la base de donn<6E>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 <20> ma<6D>triser dans ce chapitre et tout cela peut para<72>tre d<>routant la premi<6D>re fois.
Cependant, dans la pratique, tout fonctionne parfaitement. La plupart des applications Hibernate utilisent
le pattern p<>re / fils.
</para>
<para>
Nous avons <20>voqu<71> une alternative dans le premier paragraphe. Aucun des points trait<69>s pr<70>c<EFBFBD>demment n'existe
dans le cas d'un mapping <literal>&lt;composite-element&gt;</literal> qui poss<73>de exactement la s<>mantique
d'une relation p<>re / fils. Malheureusement, il y a deux grandes limitations pour les classes <20>l<EFBFBD>ments
composites : les <20>l<EFBFBD>ments composites ne peuvent contenir de collections, et ils ne peuvent <20>tre les fils
d'entit<69>s autres que l'unique parent.
</para>
</sect1>
</chapter>