1452 lines
70 KiB
XML
1452 lines
70 KiB
XML
|
<?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><cache></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><class-cache></literal> et <literal><collection-cache></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><key></literal>) et <literal><index></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><key></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><set></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><idbag></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>
|