2005-11-27 11:17:00 -05:00
<?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 > < one-to-many> </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 > < composite-element> </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,
2006-02-18 13:10:39 -05:00
par d<> faut, la persistance de cette entit<69> .
2005-11-27 11:17:00 -05:00
</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 > < 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<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 > < composite-element> </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>