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

1452 lines
70 KiB
XML
Raw Normal View History

<?xml version="1.0" encoding="iso-8859-1"?>
<chapter id="performance">
<title>Am<EFBFBD>liorer les performances</title>
<sect1 id="performance-fetching" revision="2">
<title>Strat<EFBFBD>gies de chargement</title>
<para>
Une <emphasis>strat<EFBFBD>gie de chargement</emphasis> est une strat<61>gie qu'Hibernate va
utiliser pour r<>cup<75>rer des objets associ<63>s si l'application <20> besoin de naviguer <20>
travers une association.
Les strat<61>gies de chargement peuvent <20>tre d<>clar<61>es dans les m<>ta-donn<6E>es de l'outil
de mapping objet relationnel ou surcharg<72>es par une requ<71>te de type HQL ou <literal>Criteria</literal>
particuli<6C>re.
</para>
<para>
Hibernate3 d<>finit les strat<61>gies de chargement suivantes :
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Chargement par jointure</emphasis> - Hibernate r<>cup<75>re
l'instance associ<63>e ou la collection dans un m<>me <literal>SELECT</literal>,
en utilisant un <literal>OUTER JOIN</literal>.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par select</emphasis> - Un second <literal>SELECT</literal>
est utilis<69> pour r<>cup<75>rer l'instance associ<63>e ou la collection. A moins
que vous ne d<>sactiviez explicitement le chargement tardif en sp<73>cifiant
<literal>lazy="false"</literal>, ce second select ne sera ex<65>cut<75> que lorsque
vous acc<63>derez r<>ellement <20> l'association.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par sous-select</emphasis> - Un second <literal>SELECT</literal>
est utilis<69> pour r<>cup<75>rer les associations pour toutes les entit<69>s r<>cup<75>r<EFBFBD>es dans
une requ<71>te ou un chargement pr<70>alable. A moins
que vous ne d<>sactiviez explicitement le chargement tardif en sp<73>cifiant
<literal>lazy="false"</literal>, ce second select ne sera ex<65>cut<75> que lorsque
vous acc<63>derez r<>ellement <20> l'association.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par lot</emphasis> - Il s'agit d'une strat<61>gie d'optimisation
pour le chargement par select - Hibernate r<>cup<75>re un lot
d'instances ou de collections en un seul <literal>SELECT</literal> en sp<73>cifiant
une liste de cl<63> primaire ou de cl<63> <20>trang<6E>re.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate fait <20>galement la distinction entre :
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Chargement imm<6D>diat</emphasis> - Une association, une collection ou
un attribut est charg<72> imm<6D>diatement lorsque l'objet auquel appartient cet
<20>l<EFBFBD>ment est charg<72>.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement tardif d'une collection</emphasis> - Une collection est
charg<72>e lorque l'application invoque une m<>thode sur cette collection (il s'agit
du mode de chargement par d<>faut pour les collections).
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement "super tardif" d'une collection</emphasis> - les
<20>l<EFBFBD>ments de la collection sont r<>cup<75>r<EFBFBD>s individuellement depuis la base de donn<6E>es
lorsque n<>cessaire.
Hibernate essaie de ne pas charger toute la collection en m<>moire sauf si cela est
absolument n<>cessaire (bien adapt<70> aux tr<74>s grandes collections).
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par proxy</emphasis> - une association vers un seul
objet est charg<72>e lorsqu'une m<>thode autre que le getter sur l'identifiant est
appel<65>e sur l'objet associ<63>.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement "sans proxy"</emphasis> - une association vers un seul objet
est charg<72>e lorsque l'on acc<63>de <20> cet objet. Par rapport au chargement par proxy,
cette approche est moins tardif (l'association est quand m<>me charg<72>e m<>me
si on n'acc<63>de qu'<27> l'identifiant) mais plus transparente car il n'y a pas de proxy
visible dans l'application. Cette approche requiert une instrumentation du bytecode
<20> la compilation et est rarement n<>cessaire.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement tardif des attributs</emphasis> - Un attribut ou un
objet associ<63> seul est charg<72> lorsque l'on y acc<63>de. Cette approche requiert
une instrumentation du bytecode <20> la compilation et est rarement n<>cessaire.
</para>
</listitem>
</itemizedlist>
<para>
Nous avons ici deux notions orthogonales : <emphasis>quand</emphasis> l'association est
charg<72>e et <emphasis>comment</emphasis> (quelle requ<71>te SQL est utilis<69>e). Il ne faut
pas confondre les deux. Le mode de chargement est utilis<69> pour am<61>liorer les performances.
On peut utiliser le mode tardif pour d<>finir un contrat sur quelles donn<6E>es sont toujours
accessibles sur une instance d<>tach<63>e d'une classe particuli<6C>re.
</para>
<sect2 id="performance-fetching-lazy">
<title>Travailler avec des associations charg<72>es tardivement</title>
<para>
Par d<>faut, Hibernate3 utilise le chargement tardif par select pour les collections
et le chargement tardif par proxy pour les associations vers un seul objet.
Ces valeurs par d<>faut sont valables pour la plupart des associations dans la
plupart des applications.
</para>
<para>
<emphasis>Note :</emphasis> si vous d<>finissez
<literal>hibernate.default_batch_fetch_size</literal>, Hibernate va utiliser l'optimisation
du chargement par lot pour le chargement tardif (cette optimisation peut aussi
<20>tre activ<69>e <20> un niveau de granularit<69> plus fin).
</para>
<para>
Cependant, le chargement tardif pose un probl<62>me qu'il faut connaitre. L'acc<63>s <20>
une association d<>finie comme "tardive", hors du contexte d'une session hibernate
ouverte, va conduire <20> une exception. Par exemple :
</para>
<programlisting><![CDATA[s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName")
.setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!]]></programlisting>
<para>
Etant donn<6E> que la collection des permissions n'a pas <20>t<EFBFBD> initialis<69>e
avant que la <literal>Session</literal> soit ferm<72>e, la collection n'est
pas capable de se charger. <emphasis>Hibernate ne supporte pas le chargement
tardif pour des objets d<>tach<63>s</emphasis>. La solution <20> ce probl<62>me est de
d<>placer le code qui lit la collection avant le "commit" de la transaction.
</para>
<para>
Une autre alternative est d'utiliser une collection ou une association non
"tardive" en sp<73>cifiant <literal>lazy="false"</literal> dans le mapping de
l'association.
Cependant il est pr<70>vu que le chargement tardif soit utilis<69> pour quasiment
toutes les collections ou associations. Si vous d<>finissez trop d'associtions
non "tardives" dans votre mod<6F>le objet, Hibernate va finir par devoir charger
toute la base de donn<6E>es en m<>moire <20> chaque transaction !
</para>
<para>
D'un autre c<>t<EFBFBD>, on veut souvent choisir un chargement par jointure (qui est par
d<>faut non tardif) <20> la place du chargement par select dans une transaction particuli<6C>re.
Nous allons maintenant voir comment adapter les strat<61>gies de chargement. Dans Hibernate3
les m<>canismes pour choisir une strat<61>gie de chargement sont identiques que
l'on ait une association vers un objet simple ou vers une collection.
</para>
</sect2>
<sect2 id="performance-fetching-custom" revision="4">
<title>Personnalisation des strat<61>gies de chargement</title>
<para>
Le chargement par select (mode par d<>faut) est tr<74>s vuln<6C>rable au probl<62>me du
N+1 selects, du coup vous pouvez avoir envie d'activer le chargement par jointure
dans les fichiers de mapping :
</para>
<programlisting><![CDATA[<set name="permissions"
fetch="join">
<key column="userId"/>
<one-to-many class="Permission"/>
</set]]></programlisting>
<programlisting><![CDATA[<many-to-one name="mother" class="Cat" fetch="join"/>]]></programlisting>
<para>
La strat<61>gie de chargement d<>finie <20> l'aide du mot <literal>fetch</literal> dans les fichiers
de mapping affecte :
</para>
<itemizedlist>
<listitem>
<para>
La r<>cup<75>ration via <literal>get()</literal> ou <literal>load()</literal>
</para>
</listitem>
<listitem>
<para>
La r<>cup<75>ration implicite lorsque l'on navigue <20> travers une association
</para>
</listitem>
<listitem>
<para>
Les requ<71>tes de type <literal>Criteria</literal>
</para>
</listitem>
<listitem>
<para>
Les requ<71>tes HQL si l'on utilise le chargement par <literal>subselect</literal>
</para>
</listitem>
</itemizedlist>
<para>
Quelle que soit la strat<61>gie de chargement que vous utilisez, la partie du graphe
d'objets qui est d<>finie comme non "tardive" sera charg<72>e en m<>moire. Cela peut
mener <20> l'ex<65>cution de plusieurs selects successifs pour une seule requ<71>te HQL.
</para>
<para>
On n'utilise pas souvent les documents de mapping pour adapter le chargement.
Au lieu de cela, on conserve le comportement par d<>faut et on le surcharge pour
une transaction particuli<6C>re en utilisant <literal>left join fetch</literal>
dans les requ<71>tes HQL. Cela indique <20> hibernate <20> Hibernate de charger l'association
de mani<6E>re agressive lors du premier select en utilisant une jointure externe.
Dans l'API Criteria vous pouvez utiliser la m<>thode
<literal>setFetchMode(FetchMode.JOIN)</literal>
</para>
<para>
Si vous ne vous sentez pas pr<70>t <20> modifier la strat<61>gie de chargement utilis<69>
par <literal>get()</literal> ou <literal>load()</literal>, vous pouvez juste
utiliser une requ<71>te de type <literal>Criteria</literal> comme par exemple :
</para>
<programlisting><![CDATA[User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();]]></programlisting>
<para>
(Il s'agit de l'<27>quivalent pour Hibernate de ce que d'autres outils de mapping
appellent un "fetch plan" ou "plan de chargement")
</para>
<para>
Une autre mani<6E>re compl<70>tement diff<66>rente d'<27>viter le probl<62>me des N+1 selects
est d'utiliser le cache de second niveau.
</para>
</sect2>
<sect2 id="performance-fetching-proxies" revision="2">
<title>Proxys pour des associations vers un seul objet</title>
<para>
Le chargement tardif des collections est impl<70>ment<6E> par Hibernate en utilisant
ses propres impl<70>mentations pour des collections persistantes. Si l'on veut un
chargement tardif pour des associations vers un seul objet m<>tier il faut utiliser
un autre m<>canisme. L'entit<69> qui est point<6E>e par l'association doit <20>tre masqu<71>e
derri<72>re un proxy. Hibernate impl<70>mente l'initialisation tardive des proxys sur des
objets persistents via une mise <20> jour <20> chaud du bytecode (<28> l'aide de l'excellente
librairie CGLIB).
</para>
<para>
Par d<>faut, Hibernate g<>n<EFBFBD>re des proxys (au d<>marrage) pour toutes les classes
persistantes et les utilise pour activer le chargement tardif des associations
<literal>many-to-one</literal> et <literal>one-to-one</literal>.
</para>
<para>
Le fichier de mapping peut d<>clarer une interface qui sera utilis<69>e par le proxy
d'interfa<66>age pour cette classe <20> l'aide de l'attribut <literal>proxy</literal>.
Par d<>faut Hibernate utilises une sous classe de la classe persistante.
<emphasis>Il faut que les classes pour lesquelles on ajoute un proxy impl<70>mentent
un constructeur par d<>faut de visibilit<69> au moins package. Ce constructeur est
recommand<6E> pour toutes les classes persistantes !</emphasis>
</para>
<para>
Il y a quelques pr<70>cautions <20> prendre lorsque l'on <20>tend cette approche <20> des classes
polymorphiques, exemple :
</para>
<programlisting><![CDATA[<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat" proxy="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
Tout d'abord, les instances de <literal>Cat</literal> ne pourront jamais <20>tre "cast<73>es"
en <literal>DomesticCat</literal>, m<>me si l'instance sous jacente est une instance
de <literal>DomesticCat</literal> :
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instancie un proxy (n'interroge pas la base de donn<6E>es)
if ( cat.isDomesticCat() ) { // interroge la base de donn<6E>es pour initialiser le proxy
DomesticCat dc = (DomesticCat) cat; // Erreur !
....
}]]></programlisting>
<para>
Deuxi<78>mement, il est possible de casser la notion d'<literal>==</literal> des proxy.
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(Cat.class, id); // instancie un proxy Cat
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquiert un nouveau proxy DomesticCat
System.out.println(cat==dc); // faux]]></programlisting>
<para>
Cette situation n'est pas si mauvaise qu'il n'y parait. M<>me si nous avons deux
r<>f<EFBFBD>rences <20> deux objets proxys diff<66>rents, l'instance de base sera quand m<>me le m<>me objet :
</para>
<programlisting><![CDATA[cat.setWeight(11.0); // interroge la base de donn<6E>es pour initialiser le proxy
System.out.println( dc.getWeight() ); // 11.0]]></programlisting>
<para>
Troisi<73>mement, vous ne pourrez pas utiliser un proxy CGLIB pour une classe <literal>final</literal>
ou pour une classe contenant la moindre m<>thode <literal>final</literal>.
</para>
<para>
Enfin, si votre objet persistant obtient une ressource <20> l'instanciation (par
example dans les initialiseurs ou dans le contructeur par d<>faut), alors ces ressources
seront aussi obtenues par le proxy. La classe proxy est vraiment une sous classe de la classe
persistante.
</para>
<para>
Ces probl<62>mes sont tous dus aux limitations fondamentales du mod<6F>le d'h<>ritage unique de Java.
Si vous souhaitez <20>viter ces probl<62>mes, vos classes persistantes doivent chacune impl<70>menter
une interface qui d<>clare ses m<>thodes m<>tier. Vous devriez alors sp<73>cifier ces interfaces
dans le fichier de mapping :
</para>
<programlisting><![CDATA[<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>]]></programlisting>
<para>
o<> <literal>CatImpl</literal> impl<70>mente l'interface <literal>Cat</literal> et <literal>DomesticCatImpl</literal>
impl<70>mente l'interface <literal>DomesticCat</literal>. Ainsi, des proxys pour les instances de
<literal>Cat</literal> et <literal>DomesticCat</literal> pourraient <20>tre retourn<72>es par <literal>load()</literal>
ou <literal>iterate()</literal> (Notez que <literal>list()</literal> ne retourne g<>n<EFBFBD>ralement pas de proxy).
</para>
<programlisting><![CDATA[Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();]]></programlisting>
<para>
Les relations sont aussi initialis<69>es tardivement. Ceci signifie que vous
devez d<>clarer chaque propri<72>t<EFBFBD> comme <20>tant de type <literal>Cat</literal>,
et non <literal>CatImpl</literal>.
</para>
<para>
Certaines op<6F>rations ne n<>cessitent pas l'initialisation du proxy
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<literal>equals()</literal>, si la classe persistante ne surcharge pas
<literal>equals()</literal>
</para>
</listitem>
<listitem>
<para>
<literal>hashCode()</literal>, si la classe persistante ne surcharge pas
<literal>hashCode()</literal>
</para>
</listitem>
<listitem>
<para>
Le getter de l'identifiant
</para>
</listitem>
</itemizedlist>
<para>
Hibernate d<>tectera les classes qui surchargent <literal>equals()</literal> ou
<literal>hashCode()</literal>.
</para>
<para>
Eh choisissant <literal>lazy="no-proxy"</literal> au lieu de <literal>lazy="proxy"</literal>
qui est la valeur par d<>faut, il est possible d'<27>viter les probl<62>mes li<6C>s au transtypage.
Il faudra alors une instrumentation du bytecode <20> la compilation et toutes les op<6F>rations
r<>sulterons imm<6D>diatement en une initialisation du proxy.
</para>
</sect2>
<sect2 id="performance-fetching-initialization" revision="1">
<title>Initialisation des collections et des proxys</title>
<para>
Une exception de type <literal>LazyInitializationException</literal> sera renvoy<6F>e par hibernate
si une collection ou un proxy non initialis<69> est acc<63>d<EFBFBD> en dehors de la port<72>e de la <literal>Session</literal>,
e.g. lorsque l'entit<69> <20> laquelle appartient la collection ou qui a une r<>f<EFBFBD>rence vers le proxy est
dans l'<27>tat "d<>tach<63>e".
</para>
<para>
Parfois, nous devons nous assurer qu'un proxy ou une collection est initialis<69>e avant de
fermer la <literal>Session</literal>. Bien s<>r, nous pouvons toujours forcer l'initialisation
en appelant par exemple <literal>cat.getSex()</literal> ou <literal>cat.getKittens().size()</literal>.
Mais ceci n'est pas tr<74>s lisible pour les personnes parcourant le code et n'est pas tr<74>s g<>n<EFBFBD>rique.
</para>
<para>
Les m<>thodes statiques <literal>Hibernate.initialize()</literal> et <literal>Hibernate.isInitialized()</literal>
fournissent <20> l'application un moyen de travailler avec des proxys ou des collections initialis<69>s.
<literal>Hibernate.initialize(cat)</literal> forcera l'initialisation d'un proxy de <literal>cat</literal>,
si tant est que sa <literal>Session</literal> est ouverte. <literal>Hibernate.initialize( cat.getKittens() )</literal>
a le m<>me effet sur la collection kittens.
</para>
<para>
Une autre option est de conserver la <literal>Session</literal> ouverte jusqu'<27>
ce que toutes les collections et tous les proxys aient <20>t<EFBFBD> charg<72>s. Dans certaines
architectures applicatives, particuli<6C>rement celles ou le code d'acc<63>s aux donn<6E>es
via hiberante et le code qui utilise ces donn<6E>es sont dans des couches applicatives
diff<66>rentes ou des processus physiques diff<66>rents, il peut devenir probl<62>matique
de garantir que la <literal>Session</literal> est ouverte lorsqu'une collection
est initialis<69>e. Il y a deux moyens de traiter ce probl<62>me :
</para>
<itemizedlist>
<listitem>
<para>
Dans une application web, un filtre de servlet peut <20>tre utilis<69> pour
fermer la <literal>Session</literal> uniquement lorsque la requ<71>te
a <20>t<EFBFBD> enti<74>rement trait<69>e, lorsque le rendu de la vue est fini
(il s'agit du pattern <emphasis>Open Session in View</emphasis>).
Bien s<>r, cela demande plus d'attention <20> la bonne gestion des exceptions
de l'application. Il est d'une importance vitale que la <literal>Session</literal>
soit ferm<72>e et la transaction termin<69>e avant que l'on rende la main <20> l'utilisateur
m<>me si une exception survient durant le traitement de la vue.
Voir le wiki Hibernate pour des exemples sur le pattern
"Open Session in View".
</para>
</listitem>
<listitem>
<para>
Dans une application avec une couche m<>tier s<>par<61>e, la couche contenant
la logique m<>tier doit "pr<70>parer" toutes les collections qui seront
n<>cessaires <20> la couche web avant de retourner les donn<6E>es. Cela signifie
que la couche m<>tier doit charger toutes les donn<6E>es et retourner toutes
les donn<6E>es d<>j<EFBFBD> initialis<69>es <20> la couche de pr<70>sentation/web pour un
cas d'utilisation donn<6E>. En g<>n<EFBFBD>ral l'application appelle la m<>thode
<literal>Hibernate.initialize()</literal> pour chaque collection n<>cessaire
dans la couche web (cet appel doit <20>tre fait avant la fermeture de la session)
ou bien r<>cup<75>re les collections de mani<6E>re agressive <20> l'aide d'une requ<71>te
HQL avec une clause <literal>FETCH</literal> ou <20> l'aide du mode
<literal>FetchMode.JOIN</literal> pour une requ<71>te de type <literal>Criteria</literal>.
Cela est en g<>n<EFBFBD>ral plus facile si vous utilisez le pattern <emphasis>Command</emphasis>
plut<75>t que <emphasis>Session Facade</emphasis>.
</para>
</listitem>
<listitem>
<para>
Vous pouvez <20>galement attacher <20> une <literal>Session</literal> un objet charg<72>
au pr<70>alable <20> l'aide des m<>thodes <literal>merge()</literal> ou <literal>lock()</literal>
avant d'acc<63>der aux collections (ou aux proxys) non initialis<69>s. Non, Hibernate ne
fait pas, et ne doit pas faire, cela automatiquement car cela pourrait introduire
une s<>mantique transactionnelle ad hoc.
</para>
</listitem>
</itemizedlist>
<para>
Parfois, vous ne voulez pas initialiser une grande collection mais vous avez quand m<>me
besoin d'informations sur elle (comme sa taille) ou un sous ensemble de ses donn<6E>es
</para>
<para>
Vous pouvez utiliser un filtre de collection pour r<>cup<75>rer sa taille sans l'initialiser :
</para>
<programlisting><![CDATA[( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()]]></programlisting>
<para>
La m<>thode <literal>createFilter()</literal> est <20>galement utilis<69>e pour r<>cup<75>rer
de mani<6E>re efficace des sous ensembles d'une collection sans avoir besoin de l'initialiser
dans son ensemble.
</para>
<programlisting><![CDATA[s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();]]></programlisting>
</sect2>
<sect2 id="performance-fetching-batch">
<title>Utiliser le chargement par lot</title>
<para>
Pour am<61>liorer les performances, Hibernate peut utiliser le chargement par lot
ce qui veut dire qu'Hibernate peut charger plusieurs proxys (ou collections) non initialis<69>s en une seule
requ<71>te lorsque l'on acc<63>de <20> l'un de ces proxys. Le chargement par lot est une optimisation
intimement li<6C>e <20> la strat<61>gie de chargement tardif par select. Il y a deux moyens d'activer le
chargement par lot : au niveau de la classe et au niveau de la collection.
</para>
<para>
Le chargement par lot pour les classes/entit<69>s est plus simple <20> comprendre. Imaginez que vous ayez la
situation suivante <20> l'ex<65>cution : vous avez 25 instances de <literal>Cat</literal>
charg<72>es dans une <literal>Session</literal>, chaque <literal>Cat</literal> a une r<>f<EFBFBD>rence
<20> son <literal>owner</literal>, une <literal>Person</literal>.
La classe <literal>Person</literal> est mapp<70>e avec un proxy, <literal>lazy="true"</literal>.
Si vous it<69>rez sur tous les cats et appelez <literal>getOwner()</literal> sur chacun d'eux,
Hibernate ex<65>cutera par d<>faut 25 <literal>SELECT</literal>, pour charger les owners
(initialiser le proxy). Vous pouvez param<61>trer ce comportement en sp<73>cifiant une
<literal>batch-size</literal> (taille du lot) dans le mapping de <literal>Person</literal> :
</para>
<programlisting><![CDATA[<class name="Person" batch-size="10">...</class>]]></programlisting>
<para>
Hibernate ex<65>cutera d<>sormais trois requ<71>tes, en chargeant respectivement 10,
10, et 5 entit<69>s.
</para>
<para>
Vous pouvez aussi activer le chargement par lot pour les collections. Par exemple,
si chaque <literal>Person</literal> a une collection charg<72>e tardivement de
<literal>Cat</literal>s, et que 10 personnes sont actuellement charg<72>es dans la
<literal>Session</literal>, it<69>rer sur toutes les persons g<>n<EFBFBD>rera 10 <literal>SELECT</literal>s,
un pour chaque appel de <literal>getCats()</literal>. Si vous activez le chargement par lot pour la
collection <literal>cats</literal> dans le mapping de <literal>Person</literal>, Hibernate pourra
pr<70>charger les collections :
</para>
<programlisting><![CDATA[<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>]]></programlisting>
<para>
Avec une taille de lot (<literal>batch-size</literal>) de 8, Hibernate chargera
respectivement 3, 3, 3, et 1 collections en quatre <literal>SELECT</literal>s.
Encore une fois, la valeur de l'attribut d<>pend du nombre de collections
non initialis<69>es dans une <literal>Session</literal> particuli<6C>re.
</para>
<para>
Le chargement par lot de collections est particuli<6C>rement utile si vous avez des
arborescenses r<>cursives d'<27>l<EFBFBD>ments (typiquement, le sch<63>ma facture de
mat<61>riels). (Bien qu'un <emphasis>sous ensemble</emphasis> ou un
<emphasis>chemin mat<61>rialis<69></emphasis> est sans doute une meilleure option pour
des arbres principalement en lecture.)
</para>
</sect2>
<sect2 id="performance-fetching-subselect">
<title>Utilisation du chargement par sous select</title>
<para>
Si une collection ou un proxy vers un objet doit <20>tre charg<72>, Hibernate va tous les
charger en r<>-ex<65>cutant la requ<71>te orignial dans un sous select. Cela fonctionne de la
m<>me mani<6E>re que le chargement par lot sans la possibilit<69> de fragmenter le chargement.
</para>
<!-- TODO: Write more about this -->
</sect2>
<sect2 id="performance-fetching-lazyproperties">
<title>Utiliser le chargement tardif des propri<72>t<EFBFBD>s</title>
<para>
Hibernate3 supporte le chargement tardif de propri<72>t<EFBFBD>s individuelles. La technique
d'optimisation est <20>galement connue sous le nom de <emphasis>fetch groups</emphasis> (groupes
de chargement). Il faut noter qu'il s'agit principalement d'une fonctionnalit<69> marketing
car en pratique l'optimisation de la lecture d'un enregistrement est beaucoup plus importante
que l'optimisation de la lecture d'une colonne. Cependant, la restriction du chargement <20>
certaines colonnes peut <20>tre pratique dans des cas extr<74>mes, lorsque des tables "legacy"
poss<73>dent des centaines de colonnes et que le mod<6F>le de donn<6E>es ne peut pas <20>tre am<61>lior<6F>.
</para>
<para>
Pour activer le chargement tardif d'une propri<72>t<EFBFBD>, il faut mettre l'attribut <literal>lazy</literal>
sur une propri<72>t<EFBFBD> particuli<6C>re du mapping :
</para>
<programlisting><![CDATA[<class name="Document">
<id name="id">
<generator class="native"/>
</id>
<property name="name" not-null="true" length="50"/>
<property name="summary" not-null="true" length="200" lazy="true"/>
<property name="text" not-null="true" length="2000" lazy="true"/>
</class>]]></programlisting>
<para>
Le chargement tardif des propri<72>t<EFBFBD>s requiert une instrumentation du bytecode lors de la
compilation ! Si les classes persistantes ne sont pas instrument<6E>es, Hibernate ignorera de
mani<6E>re silencieuse le mode tardif et retombera dans le mode de chargement imm<6D>diat.
</para>
<para>
Pour l'instrumentation du bytecode vous pouvez utiliser la t<>che Ant suivante :
</para>
<programlisting><![CDATA[<target name="instrument" depends="compile">
<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">
<classpath path="${jar.path}"/>
<classpath path="${classes.dir}"/>
<classpath refid="lib.class.path"/>
</taskdef>
<instrument verbose="true">
<fileset dir="${testclasses.dir}/org/hibernate/auction/model">
<include name="*.class"/>
</fileset>
</instrument>
</target>]]></programlisting>
<para>
Une autre fa<66>on (meilleure ?) pour <20>viter de lire plus de colonnes que
n<>cessaire au moins pour des transactions en lecture seule est d'utiliser
les fonctionnalit<69>s de projection des requ<71>tes HQL ou Criteria. Cela <20>vite
de devoir instrumenter le bytecode <20> la compilation et est certainement une
solution pr<70>f<EFBFBD>rable.
</para>
<para>
Vous pouvez forcer le mode de chargement agressif des propri<72>t<EFBFBD>s en utilisant
<literal>fetch all properties</literal> dans les requ<71>ts HQL.
</para>
</sect2>
</sect1>
<sect1 id="performance-cache" revision="1">
<title>Le cache de second niveau</title>
<para>
Une <literal>Session</literal> Hibernate est un cache de niveau transactionnel
des donn<6E>es persistantes. Il est possible de configurer un cache de cluster ou de JVM
(de niveau <literal>SessionFactory</literal> pour <20>tre exact) d<>fini classe par classe
et collection par collection. Vous pouvez m<>me utiliser votr choix de cache
en impl<70>mentant le pourvoyeur (provider) associ<63>.
Faites attention, les caches ne sont jamais avertis des modifications faites
dans la base de donn<6E>es par d'autres applications (ils peuvent cependant <20>tre
configur<75>s pour r<>guli<6C>rement expirer les donn<6E>es en cache).
</para>
<para>
Par d<>faut, Hibernate utilise EHCache comme cache de niveau JVM (le support
de JCS est d<>sormais d<>pr<70>ci<63> et sera enlev<65> des futures versions d'Hibernate).
Vous pouvez choisir une autre impl<70>mentation en sp<73>cifiant le nom de la classe qui
impl<70>mente <literal>org.hibernate.cache.CacheProvider</literal> en utilisant
la propri<72>t<EFBFBD> <literal>hibernate.cache.provider_class</literal>.
</para>
<table frame="topbot" id="cacheproviders" revision="1">
<title>Fournisseur de cache</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="3*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>Cache</entry>
<entry>Classe pourvoyeuse</entry>
<entry>Type</entry>
<entry>Support en Cluster</entry>
<entry>Cache de requ<71>tes support<72></entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (ne pas utiliser en production)</entry>
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
<entry>m<EFBFBD>moire</entry>
<entry></entry>
<entry>oui</entry>
</row>
<row>
<entry>EHCache</entry>
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
<entry>m<EFBFBD>moire, disque</entry>
<entry></entry>
<entry>oui</entry>
</row>
<row>
<entry>OSCache</entry>
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
<entry>m<EFBFBD>moire, disque</entry>
<entry></entry>
<entry>oui</entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry><literal>org.hibernate.cache.SwarmCacheProvider</literal></entry>
<entry>en cluster (multicast ip)</entry>
<entry>oui (invalidation de cluster)</entry>
<entry></entry>
</row>
<row>
<entry>JBoss TreeCache</entry>
<entry><literal>org.hibernate.cache.TreeCacheProvider</literal></entry>
<entry>en cluster (multicast ip), transactionnel</entry>
<entry>oui (replication)</entry>
<entry>oui (horloge sync. n<>cessaire)</entry>
</row>
</tbody>
</tgroup>
</table>
<sect2 id="performance-cache-mapping" revision="2">
<title>Mapping de Cache</title>
<para>
L'<27>l<EFBFBD>ment <literal>&lt;cache&gt;</literal> d'une classe ou d'une collection <20>
la forme suivante :
</para>
<programlistingco>
<areaspec>
<area id="cache1" coords="2 70"/>
<area id="cache2" coords="3 70"/>
<area id="cache3" coords="4 70"/>
</areaspec>
<programlisting><![CDATA[<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>]]></programlisting>
<calloutlist>
<callout arearefs="cache1">
<para>
<literal>usage</literal> (requis) sp<73>cifie la strat<61>gie de cache :
<literal>transactionel</literal>,
<literal>lecture-<2D>criture</literal>,
<literal>lecture-<2D>criture non stricte</literal> ou
<literal>lecture seule</literal>
</para>
</callout>
<callout arearefs="cache2">
<para>
<literal>region</literal> (optionnel, par d<>faut il s'agit du nom
de la classe ou du nom de role de la collection) sp<73>cifie le nom de la
r<>gion du cache de second niveau
</para>
</callout>
<callout arearefs="cache3">
<para>
<literal>include</literal> (optionnel, par d<>faut <literal>all</literal>)
<literal>non-lazy</literal> sp<73>cifie que les propri<72>t<EFBFBD>s des entit<69>s mapp<70>es avec
<literal>lazy="true"</literal> ne doivent pas <20>tre mises en cache lorsque
le chargement tardif des attributs est activ<69>.
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
Alternativement (voir pr<70>f<EFBFBD>rentiellement), vous pouvez sp<73>cifier les <20>l<EFBFBD>ments
<literal>&lt;class-cache&gt;</literal> et <literal>&lt;collection-cache&gt;</literal>
dans <literal>hibernate.cfg.xml</literal>.
</para>
<para>
L'attribut <literal>usage</literal> sp<73>cifie une <emphasis>strat<EFBFBD>gie de concurrence d'acc<63>s au cache</emphasis>.
</para>
</sect2>
<sect2 id="performance-cache-readonly">
<title>Strategie : lecture seule</title>
<para>
Si votre application a besoin de lire mais ne modifie jamais les instances d'une classe,
un cache <literal>read-only</literal> peut <20>tre utilis<69>. C'est la strat<61>gie la plus simple
et la plus performante. Elle est m<>me parfaitement s<>re dans un cluster.
</para>
<programlisting><![CDATA[<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-readwrite">
<title>Strat<EFBFBD>gie : lecture/<2F>criture</title>
<para>
Si l'application a besoin de mettre <20> jour des donn<6E>es, un cache <literal>read-write</literal> peut
<20>tre appropri<72>. Cette strat<61>gie ne devrait jamais <20>tre utilis<69>e si votre application
n<>cessite un niveau d'isolation transactionnelle s<>rialisable. Si le cache est utilis<69>
dans un environnement JTA, vous devez sp<73>cifier
<literal>hibernate.transaction.manager_lookup_class</literal>, fournissant une strat<61>gie pour obtenir
le <literal>TransactionManager</literal> JTA. Dans d'autres environnements, vous devriez vous assurer
que la transation est termin<69>e <20> l'appel de <literal>Session.close()</literal>
ou <literal>Session.disconnect()</literal>. Si vous souhaitez utiliser cette strat<61>gie
dans un cluster, vous devriez vous assurer que l'impl<70>mentation de cache utilis<69>e supporte
le v<>rrouillage. Ce que ne font <emphasis>pas</emphasis> les pourvoyeurs caches fournis.
</para>
<programlisting><![CDATA[<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>]]></programlisting>
</sect2>
<sect2 id="performance-cache-nonstrict">
<title>Strat<EFBFBD>gie : lecture/<2F>criture non stricte</title>
<para>
Si l'application besoin de mettre <20> jour les donn<6E>es de mani<6E>re occasionnelle
(qu'il est tr<74>s peu probable que deux transactions essaient de mettre <20> jour le m<>me
<20>l<EFBFBD>ment simultan<61>ment) et qu'une isolation transactionnelle stricte n'est pas n<>cessaire,
un cache <literal>nonstrict-read-write</literal> peut <20>tre appropri<72>. Si le cache est
utilis<69> dans un environnement JTA, vous devez sp<73>cifier
<literal>hibernate.transaction.manager_lookup_class</literal>. Dans d'autres
environnements, vous devriez vous assurer que la transation est termin<69>e <20> l'appel
de <literal>Session.close()</literal> ou <literal>Session.disconnect()</literal>
</para>
</sect2>
<sect2 id="performance-cache-transactional">
<title>Strat<EFBFBD>gie : transactionelle</title>
<para>
La strat<61>gie de cache <literal>transactional</literal> supporte un cache
compl<70>tement transactionnel comme, par exemple, JBoss TreeCache. Un tel cache ne
peut <20>tre utilis<69> que dans un environnement JTA et vous devez sp<73>cifier
<literal>hibernate.transaction.manager_lookup_class</literal>.
</para>
</sect2>
<para>
Aucun des caches livr<76>s ne supporte toutes les strat<61>gies de concurrence. Le tableau suivant montre
quels caches sont compatibles avec quelles strat<61>gies de concurrence.
</para>
<table frame="topbot">
<title>Strat<EFBFBD>gie de concurrence du cache</title>
<tgroup cols='5' align='left' colsep='1' rowsep='1'>
<colspec colname='c1' colwidth="1*"/>
<colspec colname='c2' colwidth="1*"/>
<colspec colname='c3' colwidth="1*"/>
<colspec colname='c4' colwidth="1*"/>
<colspec colname='c5' colwidth="1*"/>
<thead>
<row>
<entry>Cache</entry>
<entry>read-only (lecture seule)</entry>
<entry>nonstrict-read-write (lecture-<2D>criture non stricte)</entry>
<entry>read-write (lecture-<2D>riture)</entry>
<entry>transactional (transactionnel)</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (ne pas utilser en production)</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry></entry>
</row>
<row>
<entry>EHCache</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry></entry>
</row>
<row>
<entry>OSCache</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry></entry>
</row>
<row>
<entry>SwarmCache</entry>
<entry>oui</entry>
<entry>oui</entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry>JBoss TreeCache</entry>
<entry>oui</entry>
<entry></entry>
<entry></entry>
<entry>oui</entry>
</row>
</tbody>
</tgroup>
</table>
</sect1>
<sect1 id="performance-sessioncache" revision="2">
<title>G<EFBFBD>rer les caches</title>
<para>
A chaque fois que vous passez un objet <20> la m<>thode <literal>save()</literal>,
<literal>update()</literal> ou <literal>saveOrUpdate()</literal> et <20> chaque fois
que vous r<>cup<75>rez un objet avec <literal>load()</literal>, <literal>get()</literal>,
<literal>list()</literal>, <literal>iterate()</literal> or <literal>scroll()</literal>,
cet objet est ajout<75> au cache interne de la <literal>Session</literal>.
</para>
<para>
Lorsqu'il y a un appel <20> la m<>thode <literal>flush()</literal>, l'<27>tat de cet objet
va <20>tre synchronis<69> avec la base de donn<6E>es. Si vous ne voulez pas que cette synchronisation
ait lieu ou si vous traitez un grand nombre d'objets et que vous avez besoin de g<>rer
la m<>moire de mani<6E>re efficace, vous pouvez utiliser la m<>thode <literal>evict()</literal>
pour supprimer l'objet et ses collections d<>pendantes du cache de la session
</para>
<programlisting><![CDATA[ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set
while ( cats.next() ) {
Cat cat = (Cat) cats.get(0);
doSomethingWithACat(cat);
sess.evict(cat);
}]]></programlisting>
<para>
La <literal>Session</literal> dispose aussi de la m<>thode <literal>contains()</literal> pour d<>terminer
si une instance appartient au cache de la session.
</para>
<para>
Pour retirer tous les objets du cache session, appelez <literal>Session.clear()</literal>
</para>
<para>
Pour le cache de second niveau, il existe des m<>thodes d<>finies dans
<literal>SessionFactory</literal> pour retirer des instances du cache,
la classe enti<74>re, une instance de collection ou
le r<>le entier d'une collection.
</para>
<programlisting><![CDATA[sessionFactory.evict(Cat.class, catId); //evict a particular Cat
sessionFactory.evict(Cat.class); //evict all Cats
sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens
sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections]]></programlisting>
<para>
Le <literal>CacheMode</literal> contr<74>le comme une session particuli<6C>re interragit avec le
cache de second niveau
</para>
<itemizedlist>
<listitem>
<para>
<literal>CacheMode.NORMAL</literal> - lit et <20>crit les items dans le cache de second niveau
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.GET</literal> - lit les items dans le cache de second niveau mais ne
les <20>crit pas sauf dans le cache d'une mise <20> jour d'une donn<6E>e
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.PUT</literal> - <20>crit les items dans le cache de second niveau mais ne les
lit pas dans le cache de second niveau
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.REFRESH</literal> - <20>crit les items dans le cache de second niveau mais ne les
lit pas dans le cache de second niveau, outrepasse l'effet de<literal>hibernate.cache.use_minimal_puts</literal>,
en for<6F>ant un rafra<72>chissement du cache de second niveau pour chaque item lu dans la base
</para>
</listitem>
</itemizedlist>
<para>
Pour parcourir le contenu du cache de second niveau ou la r<>gion du cache d<>di<64>e au requ<71>tes, vous
pouvez utiliser l'API <literal>Statistics</literal>
API:
</para>
<programlisting><![CDATA[Map cacheEntries = sessionFactory.getStatistics()
.getSecondLevelCacheStatistics(regionName)
.getEntries();]]></programlisting>
<para>
Vous devez pour cela activer les statistiques et optionnellement forcer Hibernate <20> conserver les entr<74>es dans le
cache sous un format plus compr<70>hensible pour l'utilisateur :
</para>
<programlisting><![CDATA[hibernate.generate_statistics true
hibernate.cache.use_structured_entries true]]></programlisting>
</sect1>
<sect1 id="performance-querycache" revision="1">
<title>Le cache de requ<71>tes</title>
<para>
Les r<>sultats d'une requ<71>te peuvent aussi <20>tre plac<61>s en cache. Ceci n'est utile
que pour les requ<71>tes qui sont ex<65>cut<75>es avec les m<>mes param<61>tres. Pour utiliser
le cache de requ<71>tes, vous devez d'abord l'activer :
</para>
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
<para>
Ce param<61>tre am<61>ne la cr<63>ation de deux nouvelles r<>gions dans le cache, une qui va conserver
le r<>sultat des requ<71>tes mises en cache (<literal>org.hibernate.cache.StandardQueryCache</literal>)
et l'autre qui va conserver l'horodatage des mises <20> jour les plus r<>centes effectu<74>es sur les
tables requ<71>tables (<literal>org.hibernate.cache.UpdateTimestampsCache</literal>).
Il faut noter que le cache de requ<71>te ne conserve pas l'<27>tat des entit<69>s, il met en cache
uniquement les valeurs de l'identifiant et les valeurs de types de base (?). Le cache
de requ<71>te doit toujours <20>tre utilis<69> avec le cache de second niveau pour <20>tre efficace.
</para>
<para>
La plupart des requ<71>tes ne retirent pas de b<>n<EFBFBD>fice pas du cache,
donc par d<>faut les requ<71>tes ne sont pas mises en cache. Pour activer le cache,
appelez <literal>Query.setCacheable(true)</literal>.
Cet appel permet de v<>rifier si les r<>sultats sont en cache ou non, voire
d'ajouter ces r<>sultats si la requ<71>te est ex<65>cut<75>e.
</para>
<para>
Si vous avez besoin de contr<74>ler finement les d<>lais d'expiration du cache, vous
pouvez sp<73>cifier une r<>gion de cache nomm<6D>e pour une requ<71>te particuli<6C>re en
appelant <literal>Query.setCacheRegion()</literal>.
</para>
<programlisting><![CDATA[List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
.setEntity("blogger", blogger)
.setMaxResults(15)
.setCacheable(true)
.setCacheRegion("frontpages")
.list();]]></programlisting>
<para>
Si une requ<71>te doit forcer le rafra<72>chissement de sa r<>gion de cache, vous devez
appeler <literal>Query.setCacheMode(CacheMode.REFRESH)</literal>. C'est particuli<6C>rement
utile lorsque les donn<6E>es peuvent avoir <20>t<EFBFBD> mises <20> jour par un processus s<>par<61> (e.g. elles
n'ont pas <20>t<EFBFBD> modifi<66>es par Hibernate). Cela permet <20> l'application de rafra<72>chir de
mani<6E>re s<>lective les r<>sultats d'une requ<71>te particuli<6C>re. Il s'agit d'une alternative plus
efficace <20> l'<27>viction d'une r<>gion du cache <20> l'aide de la m<>thode
<literal>SessionFactory.evictQueries()</literal>.
</para>
</sect1>
<sect1 id="performance-collections">
<title>Comprendre les performances des Collections</title>
<para>
Nous avons d<>j<EFBFBD> pass<73> du temps <20> discuter des collections.
Dans cette section, nous allons traiter du comportement des
collections <20> l'ex<65>cution.
</para>
<sect2 id="performance-collections-taxonomy">
<title>Classification</title>
<para>Hibernate d<>finit trois types de collections :</para>
<itemizedlist>
<listitem>
<para>les collections de valeurs</para>
</listitem>
<listitem>
<para>les associations un-vers-plusieurs</para>
</listitem>
<listitem>
<para>les associations plusieurs-vers-plusieurs</para>
</listitem>
</itemizedlist>
<para>
Cette classification distingue les diff<66>rentes relations entre les tables
et les cl<63>s <20>trang<6E>res mais ne nous apprend rien de ce que nous devons savoir
sur le mod<6F>le relationnel. Pour comprendre parfaitement la structure relationnelle
et les caract<63>ristiques des performances, nous devons consid<69>rer la structure
de la cl<63> primaire qui est utilis<69>e par Hibernate pour mettre <20> jour ou supprimer
les <20>l<EFBFBD>ments des collections. Cel<65> nous am<61>ne aux classifications suivantes :
</para>
<itemizedlist>
<listitem>
<para>collections index<65>es</para>
</listitem>
<listitem>
<para>sets</para>
</listitem>
<listitem>
<para>bags</para>
</listitem>
</itemizedlist>
<para>
Toutes les collections index<65>es (maps, lists, arrays) ont une cl<63> primaire constitu<74>e
des colonnes cl<63> (<literal>&lt;key&gt;</literal>) et <literal>&lt;index&gt;</literal>.
Avec ce type de cl<63> primaire, la mise <20> jour de collection est en g<>n<EFBFBD>ral tr<74>s performante - la cl<63>
primaire peut <20>tre index<65>es efficacement et un <20>l<EFBFBD>ment particulier peut <20>tre
localis<69> efficacement lorsqu'Hibernate essaie de le mettre <20> jour ou de le supprimer.
</para>
<para>
Les Sets ont une cl<63> primaire compos<6F>e de <literal>&lt;key&gt;</literal> et des
colonnes repr<70>sentant l'<27>l<EFBFBD>ment. Elle est donc moins efficace pour certains
types de collections d'<27>l<EFBFBD>ments, en particulier les <20>l<EFBFBD>ments composites,
les textes volumineux ou les champs binaires ; la base de donn<6E>es
peut ne pas <20>tre capable d'indexer aussi efficacement une cl<63> primaire
aussi complexe. Cependant, pour les associations un-vers-plusieurs
ou plusieurs-vers-plusieurs, sp<73>cialement lorsque l'on utilise des entit<69>s
ayant des identifiants techniques, il est probable que cela soit aussi efficace
(note : si vous voulez que <literal>SchemaExport</literal> cr<63><72> effectivement
la cl<63> primaire d'un <literal>&lt;set&gt;</literal> pour vous, vous devez
d<>clarer toutes les colonnes avec <literal>not-null="true"</literal>).
</para>
<para>
Le mapping <20> l'aide d'<literal>&lt;idbag&gt;</literal> d<>finit une cl<63>
de substitution ce qui leur permet d'<27>tre tr<74>s efficaces lors de la
mise <20> jour. En fait il s'agit du meilleur cas de mise <20> jour d'une collection
</para>
<para>
Le pire cas intervient pour les Bags. Dans la mesure o<> un bag permet
la duplications des <20>l<EFBFBD>ments et n'a pas de colonne d'index, aucune cl<63> primaire
ne peut <20>tre d<>finie. Hibernate n'a aucun moyen de distinguer des enregistrements
dupliqu<71>s. Hibernate r<>sout ce probl<62>me en supprimant compl<70>tement les
enregistrements (via un simple <literal>DELETE</literal>), puis en recr<63>ant
la collection chaque fois qu'elle change. Ce qui peut <20>tre tr<74>s inefficace.
</para>
<para>
Notez que pour une relation un-vers-plusieurs, la "cl<63> primaire"
peut ne pas <20>tre la cl<63> primaire de la table en base de donn<6E>es -
mais m<>me dans ce cas, la classification ci-dessus reste utile
(Elle explique comment Hibernate "localise" chaque enregistrement
de la collection).
</para>
</sect2>
<sect2 id="performance-collections-mostefficientupdate">
<title>Les lists, les maps, les idbags et les sets sont les collections les plus efficaces pour la mise <20> jour</title>
<para>
La discussion pr<70>c<EFBFBD>dente montre clairement que les collections index<65>es
et (la plupart du temps) les sets, permettent de r<>aliser le plus efficacement
les op<6F>rations d'ajout, de suppression ou de modification d'<27>l<EFBFBD>ments.
</para>
<para>
Il existe un autre avantage qu'ont les collections index<65>es sur les Sets
dans le cadre d'une association plusieurs vers plusieurs ou d'une collection de valeurs.
A cause de la structure inh<6E>rente d'un <literal>Set</literal>, Hibernate n'effectue jamais
d'<literal>UPDATE</literal> quand un enregistrement est modifi<66>. Les modifications
apport<72>es <20> un <literal>Set</literal> se font via un <literal>INSERT</literal> et <literal>DELETE</literal>
(de chaque enregistrement). Une fois de plus, ce cas ne s'applique pas aux associations
un vers plusieurs.
</para>
<para>
Apr<70>s s'<27>tre rappel<65> que les tableaux ne peuvent pas <20>tre charg<72>s tardivement,
nous pouvons conclure que les lists, les maps et les idbags sont les types de collections
(non invers<72>es) les plus performants, avec les sets pas loin derri<72>res.
Les sets son le type de collection le plus courant dans les applications Hibernate. Cela
est du au fait que la s<>mantique des "set" est la plus naturelle dans le mod<6F>le
relationnel.
</para>
<para>
Cependant, dans des mod<6F>les objet bien con<6F>us avec Hibernate, on voit souvent que
la plupart des collections sont en fait des associations "un-vers-plusieurs" avec
<literal>inverse="true"</literal>. Pour ces associations, les mises <20> jour sont g<>r<EFBFBD>es
au niveau de l'association "plusieurs-vers-un" et les consid<69>rations de performance de
mise <20> jour des collections ne s'appliquent tout simplement pas dans ces cas l<>.
</para>
</sect2>
<sect2 id="performance-collections-mostefficentinverse">
<title>Les Bags et les lists sont les plus efficaces pour les collections inverse</title>
<para>
Avant que vous n'oubliez les bags pour toujours, il y a un cas pr<70>cis o<> les bags
(et les lists) sont bien plus performants que les sets. Pour une collection marqu<71>e
comme <literal>inverse="true"</literal> (le choix le plus courant pour un relation
un vers plusieurs bidirectionnelle), nous pouvons ajouter des <20>l<EFBFBD>ments <20> un bag
ou une list sans avoir besoin de l'initialiser (fetch) les <20>l<EFBFBD>ments du sac!
Ceci parce que <literal>Collection.add()</literal> ou <literal>Collection.addAll()</literal>
doit toujours retourner vrai pour un bag ou une <literal>List</literal>
(contrairement au <literal>Set</literal>).
Cela peut rendre le code suivant beaucoup plus rapide.
</para>
<programlisting><![CDATA[Parent p = (Parent) sess.load(Parent.class, id);
Child c = new Child();
c.setParent(p);
p.getChildren().add(c); //pas besoin de charger la collection !
sess.flush();]]></programlisting>
</sect2>
<sect2 id="performance-collections-oneshotdelete">
<title>Suppression en un coup</title>
<para>
Parfois, effacer les <20>l<EFBFBD>ments d'une collection un par un peut <20>tre extr<74>mement inefficace.
Hibernate n'est pas totalement stupide, il sait qu'il ne faut pas le faire dans le cas d'une
collection compl<70>tement vid<69>e (lorsque vous appellez <literal>list.clear()</literal>, par exemple).
Dans ce cas, Hibernate fera un simple <literal>DELETE</literal> et le travail est fait !
</para>
<para>
Supposons que nous ajoutions un <20>l<EFBFBD>ment dans une collection de taille vingt et que nous
enlevions ensuite deux <20>l<EFBFBD>ments. Hibernate effectuera un <literal>INSERT</literal> puis
deux <literal>DELETE</literal> (<28> moins que la collection ne soit un bag). Ce qui est
souhaitable.
</para>
<para>
Cependant, supposons que nous enlevions dix huit <20>l<EFBFBD>ments, laissant ainsi deux <20>l<EFBFBD>ments, puis
que nous ajoutions trois nouveaux <20>l<EFBFBD>ments. Il y a deux moyens de proc<6F>der.
</para>
<itemizedlist>
<listitem>
<para>effacer dix huit enregistrements un <20> un puis en ins<6E>rer trois</para>
</listitem>
<listitem>
<para>effacer la totalit<69> de la collection (en un <literal>DELETE</literal> SQL) puis ins<6E>rer
les cinq <20>l<EFBFBD>ments restant un <20> un</para>
</listitem>
</itemizedlist>
<para>
Hibernate n'est pas assez intelligent pour savoir que, dans ce cas, la seconde m<>thode est plus
rapide (Il plut<75>t heureux qu'Hibernate ne soit pas trop intelligent ; un tel comportement
pourrait rendre l'utilisation de triggers de bases de donn<6E>es plut<75>t al<61>atoire, etc...).
</para>
<para>
Heureusement, vous pouvez forcer ce comportement lorsque vous le souhaitez, en liberant
(c'est-<2D>-dire en d<>r<EFBFBD>f<EFBFBD>ren<65>ant) la collection initiale et en retournant une collection
nouvellement instanci<63>e avec les <20>l<EFBFBD>ments restants. Ceci peut <20>tre tr<74>s pratique et
tr<74>s puissant de temps en temps.
</para>
<para>
Bien s<>r, la suppression en un coup ne s'applique pas pour les collections qui sont mapp<70>es
avec <literal>inverse="true"</literal>.
</para>
</sect2>
</sect1>
<sect1 id="performance-monitoring" revision="1">
<title>Moniteur de performance</title>
<para>
L'optimisation n'est pas d'un grand int<6E>r<EFBFBD>t sans le suivi et l'acc<63>s aux donn<6E>es de
performance. Hibernate fournit toute une panoplie de rapport sur ses op<6F>rations internes.
Les statistiques dans Hibernate sont fournies par <literal>SessionFactory</literal>.
</para>
<sect2 id="performance-monitoring-sf" revision="2">
<title>Suivi d'une SessionFactory</title>
<para>
Vous pouvez acc<63>der au m<>triques d'une <literal>SessionFactory</literal> de deux
mani<6E>res. La premi<6D>re option est d'appeler <literal>sessionFactory.getStatistics()</literal>
et de lire ou d'afficher les <literal>Statistics</literal> vous m<>me.
</para>
<para>
Hibernate peut <20>galement utiliser JMX pour publier les m<>triques si vous activez
le MBean <literal>StatisticsService</literal>. Vous pouvez activer un seul MBean
pour toutes vos <literal>SessionFactory</literal> ou un par factory. Voici un code
qui montre un exemple de configuration minimaliste :
</para>
<programlisting><![CDATA[// MBean service registration for a specific SessionFactory
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "myFinancialApp");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory
server.registerMBean(stats, on); // Register the Mbean on the server]]></programlisting>
<programlisting><![CDATA[// MBean service registration for all SessionFactory's
Hashtable tb = new Hashtable();
tb.put("type", "statistics");
tb.put("sessionFactory", "all");
ObjectName on = new ObjectName("hibernate", tb); // MBean object name
StatisticsService stats = new StatisticsService(); // MBean implementation
server.registerMBean(stats, on); // Register the MBean on the server]]></programlisting>
<para>
TODO: Cela n'a pas de sens : dans le premier cs on r<>cup<75>re et on utilise le MBean directement.
Dans le second, on doit fournir le nom JNDI sous lequel est retenu la fabrique de session avant de
l'utiliser. Pour cela il faut utiliser
<literal>hibernateStatsBean.setSessionFactoryJNDIName("my/JNDI/Name")</literal>
</para>
<para>
Vous pouvez (d<>s)activer le suivi pour une <literal>SessionFactory</literal>
</para>
<itemizedlist>
<listitem>
<para>
au moment de la configuration en mettant <literal>hibernate.generate_statistics</literal> <20> <literal>false</literal>
</para>
</listitem>
</itemizedlist>
<itemizedlist>
<listitem>
<para>
<20> chaud avec <literal>sf.getStatistics().setStatisticsEnabled(true)</literal>
ou <literal>hibernateStatsBean.setStatisticsEnabled(true)</literal>
</para>
</listitem>
</itemizedlist>
<para>
Les statistiques peuvent <20>tre remises <20> z<>ro de mani<6E>re programmatique <20> l'aide de la m<>thode
<literal>clear()</literal>
Un r<>sum<75> peut <20>tre envoy<6F> <20> un logger (niveau info) <20> l'aide de la m<>thode <literal>logSummary()</literal>
</para>
</sect2>
<sect2 id="performance-monitoring-metrics" revision="1">
<title>M<EFBFBD>triques</title>
<para>
Hibernate fournit un certain nombre de m<>triques, qui vont des informations tr<74>s basiques
aux informations tr<74>s sp<73>cialis<69>es qui ne sont appropri<72>es que dans certains scenarii.
Tous les compteurs accessibles sont d<>crits dans l'API de l'interface
<literal>Statistics</literal> dans trois cat<61>gories :
</para>
<itemizedlist>
<listitem>
<para>
Les m<>triques relatives <20> l'usage g<>n<EFBFBD>ral de la <literal>Session</literal>
comme le nombre de sessions ouvertes, le nombre de connexions JDBC r<>cup<75>r<EFBFBD>es, etc...
</para>
</listitem>
<listitem>
<para>
Les m<>triques relatives aux entit<69>s, collections, requ<71>tes et caches dans
leur ensemble (m<>triques globales),
</para>
</listitem>
<listitem>
<para>
Les m<>triques d<>taill<6C>es relatives <20> une entit<69>, une collection, une requ<71>te
ou une r<>gion de cache particuli<6C>re.
</para>
</listitem>
</itemizedlist>
<para>
Par exemple, vous pouvez v<>rifier l'acc<63>s au cache ainsi que le taux d'<27>l<EFBFBD>ments manquants et
de mise <20> jour des entit<69>s, collections et requ<71>tes et le temps moyen que met une requ<71>te.
Il faut faire attention au fait que le nombre de millisecondes est sujet <20> approximation en
Java. Hibernate est li<6C> <20> la pr<70>cision de la machine virtuelle, sur certaines plateformes,
cela n'offre qu'une pr<70>cision de l'ordre de 10 secondes.
</para>
<para>
Des accesseurs simples sont utilis<69>s pour acc<63>der aux m<>triques globales (e.g. celles qui ne
sont pas li<6C>es <20> une entit<69>, collection ou r<>gion de cache particuli<6C>re). Vous pouvez acc<63>der
aux m<>triques d'une entit<69>, collection, r<>gion de cache particuli<6C>re <20> l'aide de son nom et <20> l'aide
de sa repr<70>sentation HQL ou SQL pour une requ<71>te. R<>f<EFBFBD>rez vous <20> la javadoc des APIS
<literal>Statistics</literal>, <literal>EntityStatistics</literal>,
<literal>CollectionStatistics</literal>, <literal>SecondLevelCacheStatistics</literal>,
and <literal>QueryStatistics</literal> pour plus d'informations. Le code ci-dessous montre
un exemple simple :
</para>
<programlisting><![CDATA[Statistics stats = HibernateUtil.sessionFactory.getStatistics();
double queryCacheHitCount = stats.getQueryCacheHitCount();
double queryCacheMissCount = stats.getQueryCacheMissCount();
double queryCacheHitRatio =
queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);
log.info("Query Hit ratio:" + queryCacheHitRatio);
EntityStatistics entityStats =
stats.getEntityStatistics( Cat.class.getName() );
long changes =
entityStats.getInsertCount()
+ entityStats.getUpdateCount()
+ entityStats.getDeleteCount();
log.info(Cat.class.getName() + " changed " + changes + "times" );]]></programlisting>
<para>
Pour travailler sur toutes les entit<69>s, collections, requ<71>tes et r<>gions de cache, vous pouvez
r<>cup<75>rer la liste des noms des entit<69>s, collections, requ<71>tes et r<>gions de cache avec les
m<>thodes : <literal>getQueries()</literal>, <literal>getEntityNames()</literal>,
<literal>getCollectionRoleNames()</literal>, et
<literal>getSecondLevelCacheRegionNames()</literal>.
</para>
</sect2>
</sect1>
</chapter>