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

1452 lines
70 KiB
XML

<?xml version="1.0" encoding="iso-8859-1"?>
<chapter id="performance">
<title>Améliorer les performances</title>
<sect1 id="performance-fetching" revision="2">
<title>Stratégies de chargement</title>
<para>
Une <emphasis>stratégie de chargement</emphasis> est une stratégie qu'Hibernate va
utiliser pour récupérer des objets associés si l'application à besoin de naviguer à
travers une association.
Les stratégies de chargement peuvent être déclarées dans les méta-données de l'outil
de mapping objet relationnel ou surchargées par une requête de type HQL ou <literal>Criteria</literal>
particulière.
</para>
<para>
Hibernate3 définit les stratégies de chargement suivantes :
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Chargement par jointure</emphasis> - Hibernate récupère
l'instance associé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é pour récupérer l'instance associée ou la collection. A moins
que vous ne désactiviez explicitement le chargement tardif en spécifiant
<literal>lazy="false"</literal>, ce second select ne sera exécuté que lorsque
vous accéderez réellement à l'association.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par sous-select</emphasis> - Un second <literal>SELECT</literal>
est utilisé pour récupérer les associations pour toutes les entités récupérées dans
une requête ou un chargement préalable. A moins
que vous ne désactiviez explicitement le chargement tardif en spécifiant
<literal>lazy="false"</literal>, ce second select ne sera exécuté que lorsque
vous accéderez réellement à l'association.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par lot</emphasis> - Il s'agit d'une stratégie d'optimisation
pour le chargement par select - Hibernate récupère un lot
d'instances ou de collections en un seul <literal>SELECT</literal> en spécifiant
une liste de clé primaire ou de clé étrangère.
</para>
</listitem>
</itemizedlist>
<para>
Hibernate fait également la distinction entre :
</para>
<itemizedlist>
<listitem>
<para>
<emphasis>Chargement immédiat</emphasis> - Une association, une collection ou
un attribut est chargé immédiatement lorsque l'objet auquel appartient cet
élément est chargé.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement tardif d'une collection</emphasis> - Une collection est
chargé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
éléments de la collection sont récupérés individuellement depuis la base de donné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é aux très grandes collections).
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement par proxy</emphasis> - une association vers un seul
objet est chargée lorsqu'une méthode autre que le getter sur l'identifiant est
appelée sur l'objet associé.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement "sans proxy"</emphasis> - une association vers un seul objet
est chargée lorsque l'on accède à cet objet. Par rapport au chargement par proxy,
cette approche est moins tardif (l'association est quand même chargée même
si on n'accède qu'à l'identifiant) mais plus transparente car il n'y a pas de proxy
visible dans l'application. Cette approche requiert une instrumentation du bytecode
à la compilation et est rarement nécessaire.
</para>
</listitem>
<listitem>
<para>
<emphasis>Chargement tardif des attributs</emphasis> - Un attribut ou un
objet associé seul est chargé lorsque l'on y accède. Cette approche requiert
une instrumentation du bytecode à la compilation et est rarement nécessaire.
</para>
</listitem>
</itemizedlist>
<para>
Nous avons ici deux notions orthogonales : <emphasis>quand</emphasis> l'association est
chargée et <emphasis>comment</emphasis> (quelle requête SQL est utilisée). Il ne faut
pas confondre les deux. Le mode de chargement est utilisé pour améliorer les performances.
On peut utiliser le mode tardif pour définir un contrat sur quelles données sont toujours
accessibles sur une instance détachée d'une classe particulière.
</para>
<sect2 id="performance-fetching-lazy">
<title>Travailler avec des associations chargé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
être activée à un niveau de granularité plus fin).
</para>
<para>
Cependant, le chargement tardif pose un problème qu'il faut connaitre. L'accès à
une association définie comme "tardive", hors du contexte d'une session hibernate
ouverte, va conduire à 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é que la collection des permissions n'a pas été initialisée
avant que la <literal>Session</literal> soit fermée, la collection n'est
pas capable de se charger. <emphasis>Hibernate ne supporte pas le chargement
tardif pour des objets détachés</emphasis>. La solution à ce problè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écifiant <literal>lazy="false"</literal> dans le mapping de
l'association.
Cependant il est prévu que le chargement tardif soit utilisé pour quasiment
toutes les collections ou associations. Si vous définissez trop d'associtions
non "tardives" dans votre modèle objet, Hibernate va finir par devoir charger
toute la base de données en mémoire à chaque transaction !
</para>
<para>
D'un autre côté, on veut souvent choisir un chargement par jointure (qui est par
défaut non tardif) à la place du chargement par select dans une transaction particulière.
Nous allons maintenant voir comment adapter les stratégies de chargement. Dans Hibernate3
les mécanismes pour choisir une straté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égies de chargement</title>
<para>
Le chargement par select (mode par défaut) est très vulnérable au problè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égie de chargement définie à l'aide du mot <literal>fetch</literal> dans les fichiers
de mapping affecte :
</para>
<itemizedlist>
<listitem>
<para>
La récupération via <literal>get()</literal> ou <literal>load()</literal>
</para>
</listitem>
<listitem>
<para>
La récupération implicite lorsque l'on navigue à travers une association
</para>
</listitem>
<listitem>
<para>
Les requêtes de type <literal>Criteria</literal>
</para>
</listitem>
<listitem>
<para>
Les requêtes HQL si l'on utilise le chargement par <literal>subselect</literal>
</para>
</listitem>
</itemizedlist>
<para>
Quelle que soit la stratégie de chargement que vous utilisez, la partie du graphe
d'objets qui est définie comme non "tardive" sera chargée en mémoire. Cela peut
mener à l'exécution de plusieurs selects successifs pour une seule requê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ère en utilisant <literal>left join fetch</literal>
dans les requêtes HQL. Cela indique à hibernate à Hibernate de charger l'association
de maniè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êt à modifier la stratégie de chargement utilisé
par <literal>get()</literal> ou <literal>load()</literal>, vous pouvez juste
utiliser une requê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'équivalent pour Hibernate de ce que d'autres outils de mapping
appellent un "fetch plan" ou "plan de chargement")
</para>
<para>
Une autre manière complètement différente d'éviter le problè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émenté par Hibernate en utilisant
ses propres implé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é qui est pointée par l'association doit être masquée
derrière un proxy. Hibernate implémente l'initialisation tardive des proxys sur des
objets persistents via une mise à jour à chaud du bytecode (à l'aide de l'excellente
librairie CGLIB).
</para>
<para>
Par défaut, Hibernate génè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ée par le proxy
d'interfaçage pour cette classe à 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émentent
un constructeur par défaut de visibilité au moins package. Ce constructeur est
recommandé pour toutes les classes persistantes !</emphasis>
</para>
<para>
Il y a quelques précautions à prendre lorsque l'on étend cette approche à 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 être "casté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ées)
if ( cat.isDomesticCat() ) { // interroge la base de données pour initialiser le proxy
DomesticCat dc = (DomesticCat) cat; // Erreur !
....
}]]></programlisting>
<para>
Deuxiè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érences à deux objets proxys diffé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ées pour initialiser le proxy
System.out.println( dc.getWeight() ); // 11.0]]></programlisting>
<para>
Troisiè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 à 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èmes sont tous dus aux limitations fondamentales du modèle d'héritage unique de Java.
Si vous souhaitez éviter ces problèmes, vos classes persistantes doivent chacune implémenter
une interface qui déclare ses méthodes métier. Vous devriez alors spé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>
<literal>CatImpl</literal> implémente l'interface <literal>Cat</literal> et <literal>DomesticCatImpl</literal>
implémente l'interface <literal>DomesticCat</literal>. Ainsi, des proxys pour les instances de
<literal>Cat</literal> et <literal>DomesticCat</literal> pourraient être retournées par <literal>load()</literal>
ou <literal>iterate()</literal> (Notez que <literal>list()</literal> ne retourne géné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ées tardivement. Ceci signifie que vous
devez déclarer chaque propriété comme étant de type <literal>Cat</literal>,
et non <literal>CatImpl</literal>.
</para>
<para>
Certaines opé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'éviter les problèmes liés au transtypage.
Il faudra alors une instrumentation du bytecode à la compilation et toutes les opérations
résulterons immé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ée par hibernate
si une collection ou un proxy non initialisé est accédé en dehors de la portée de la <literal>Session</literal>,
e.g. lorsque l'entité à laquelle appartient la collection ou qui a une référence vers le proxy est
dans l'état "détachée".
</para>
<para>
Parfois, nous devons nous assurer qu'un proxy ou une collection est initialisé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ès lisible pour les personnes parcourant le code et n'est pas très générique.
</para>
<para>
Les méthodes statiques <literal>Hibernate.initialize()</literal> et <literal>Hibernate.isInitialized()</literal>
fournissent à l'application un moyen de travailler avec des proxys ou des collections initialisé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'à
ce que toutes les collections et tous les proxys aient été chargés. Dans certaines
architectures applicatives, particulièrement celles ou le code d'accès aux données
via hiberante et le code qui utilise ces données sont dans des couches applicatives
différentes ou des processus physiques différents, il peut devenir problématique
de garantir que la <literal>Session</literal> est ouverte lorsqu'une collection
est initialisée. Il y a deux moyens de traiter ce problème :
</para>
<itemizedlist>
<listitem>
<para>
Dans une application web, un filtre de servlet peut être utilisé pour
fermer la <literal>Session</literal> uniquement lorsque la requête
a été entièrement traité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 à la bonne gestion des exceptions
de l'application. Il est d'une importance vitale que la <literal>Session</literal>
soit fermée et la transaction terminée avant que l'on rende la main à 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ée, la couche contenant
la logique métier doit "préparer" toutes les collections qui seront
nécessaires à la couche web avant de retourner les données. Cela signifie
que la couche métier doit charger toutes les données et retourner toutes
les données déjà initialisées à la couche de présentation/web pour un
cas d'utilisation donné. En général l'application appelle la méthode
<literal>Hibernate.initialize()</literal> pour chaque collection nécessaire
dans la couche web (cet appel doit être fait avant la fermeture de la session)
ou bien récupère les collections de manière agressive à l'aide d'une requête
HQL avec une clause <literal>FETCH</literal> ou à l'aide du mode
<literal>FetchMode.JOIN</literal> pour une requête de type <literal>Criteria</literal>.
Cela est en général plus facile si vous utilisez le pattern <emphasis>Command</emphasis>
plutôt que <emphasis>Session Facade</emphasis>.
</para>
</listitem>
<listitem>
<para>
Vous pouvez également attacher à une <literal>Session</literal> un objet chargé
au préalable à l'aide des méthodes <literal>merge()</literal> ou <literal>lock()</literal>
avant d'accéder aux collections (ou aux proxys) non initialisé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ées
</para>
<para>
Vous pouvez utiliser un filtre de collection pour récupé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 également utilisée pour récupérer
de maniè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éliorer les performances, Hibernate peut utiliser le chargement par lot
ce qui veut dire qu'Hibernate peut charger plusieurs proxys (ou collections) non initialisés en une seule
requête lorsque l'on accède à l'un de ces proxys. Le chargement par lot est une optimisation
intimement liée à la straté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és est plus simple à comprendre. Imaginez que vous ayez la
situation suivante à l'exécution : vous avez 25 instances de <literal>Cat</literal>
chargées dans une <literal>Session</literal>, chaque <literal>Cat</literal> a une référence
à son <literal>owner</literal>, une <literal>Person</literal>.
La classe <literal>Person</literal> est mappée avec un proxy, <literal>lazy="true"</literal>.
Si vous itérez sur tous les cats et appelez <literal>getOwner()</literal> sur chacun d'eux,
Hibernate exécutera par défaut 25 <literal>SELECT</literal>, pour charger les owners
(initialiser le proxy). Vous pouvez paramétrer ce comportement en spé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écutera désormais trois requêtes, en chargeant respectivement 10,
10, et 5 entité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ée tardivement de
<literal>Cat</literal>s, et que 10 personnes sont actuellement chargées dans la
<literal>Session</literal>, itérer sur toutes les persons géné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é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ées dans une <literal>Session</literal> particulière.
</para>
<para>
Le chargement par lot de collections est particulièrement utile si vous avez des
arborescenses récursives d'éléments (typiquement, le schéma facture de
matériels). (Bien qu'un <emphasis>sous ensemble</emphasis> ou un
<emphasis>chemin matérialisé</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 être chargé, Hibernate va tous les
charger en ré-exécutant la requête orignial dans un sous select. Cela fonctionne de la
même manière que le chargement par lot sans la possibilité de fragmenter le chargement.
</para>
<!-- TODO: Write more about this -->
</sect2>
<sect2 id="performance-fetching-lazyproperties">
<title>Utiliser le chargement tardif des propriétés</title>
<para>
Hibernate3 supporte le chargement tardif de propriétés individuelles. La technique
d'optimisation est également connue sous le nom de <emphasis>fetch groups</emphasis> (groupes
de chargement). Il faut noter qu'il s'agit principalement d'une fonctionnalité 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 à
certaines colonnes peut être pratique dans des cas extrèmes, lorsque des tables "legacy"
possèdent des centaines de colonnes et que le modèle de données ne peut pas être amélioré.
</para>
<para>
Pour activer le chargement tardif d'une propriété, il faut mettre l'attribut <literal>lazy</literal>
sur une propriété particuliè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étés requiert une instrumentation du bytecode lors de la
compilation ! Si les classes persistantes ne sont pas instrumentées, Hibernate ignorera de
manière silencieuse le mode tardif et retombera dans le mode de chargement immé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çon (meilleure ?) pour éviter de lire plus de colonnes que
nécessaire au moins pour des transactions en lecture seule est d'utiliser
les fonctionnalités de projection des requêtes HQL ou Criteria. Cela évite
de devoir instrumenter le bytecode à la compilation et est certainement une
solution préférable.
</para>
<para>
Vous pouvez forcer le mode de chargement agressif des propriétés en utilisant
<literal>fetch all properties</literal> dans les requê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ées persistantes. Il est possible de configurer un cache de cluster ou de JVM
(de niveau <literal>SessionFactory</literal> pour être exact) défini classe par classe
et collection par collection. Vous pouvez même utiliser votr choix de cache
en implémentant le pourvoyeur (provider) associé.
Faites attention, les caches ne sont jamais avertis des modifications faites
dans la base de données par d'autres applications (ils peuvent cependant être
configurés pour régulièrement expirer les donné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écié et sera enlevé des futures versions d'Hibernate).
Vous pouvez choisir une autre implémentation en spécifiant le nom de la classe qui
implémente <literal>org.hibernate.cache.CacheProvider</literal> en utilisant
la propriété <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êtes supporté</entry>
</row>
</thead>
<tbody>
<row>
<entry>Hashtable (ne pas utiliser en production)</entry>
<entry><literal>org.hibernate.cache.HashtableCacheProvider</literal></entry>
<entry>mémoire</entry>
<entry></entry>
<entry>oui</entry>
</row>
<row>
<entry>EHCache</entry>
<entry><literal>org.hibernate.cache.EhCacheProvider</literal></entry>
<entry>mémoire, disque</entry>
<entry></entry>
<entry>oui</entry>
</row>
<row>
<entry>OSCache</entry>
<entry><literal>org.hibernate.cache.OSCacheProvider</literal></entry>
<entry>mé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'élément <literal>&lt;cache&gt;</literal> d'une classe ou d'une collection à
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écifie la stratégie de cache :
<literal>transactionel</literal>,
<literal>lecture-écriture</literal>,
<literal>lecture-é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é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écifie que les propriétés des entités mappées avec
<literal>lazy="true"</literal> ne doivent pas être mises en cache lorsque
le chargement tardif des attributs est activé.
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
Alternativement (voir préférentiellement), vous pouvez spécifier les élé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écifie une <emphasis>stratégie de concurrence d'accè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 être utilisé. C'est la straté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égie : lecture/écriture</title>
<para>
Si l'application a besoin de mettre à jour des données, un cache <literal>read-write</literal> peut
être approprié. Cette stratégie ne devrait jamais être utilisée si votre application
nécessite un niveau d'isolation transactionnelle sérialisable. Si le cache est utilisé
dans un environnement JTA, vous devez spécifier
<literal>hibernate.transaction.manager_lookup_class</literal>, fournissant une stratégie pour obtenir
le <literal>TransactionManager</literal> JTA. Dans d'autres environnements, vous devriez vous assurer
que la transation est terminée à l'appel de <literal>Session.close()</literal>
ou <literal>Session.disconnect()</literal>. Si vous souhaitez utiliser cette stratégie
dans un cluster, vous devriez vous assurer que l'implémentation de cache utilisé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égie : lecture/écriture non stricte</title>
<para>
Si l'application besoin de mettre à jour les données de manière occasionnelle
(qu'il est très peu probable que deux transactions essaient de mettre à jour le même
élément simultanément) et qu'une isolation transactionnelle stricte n'est pas nécessaire,
un cache <literal>nonstrict-read-write</literal> peut être approprié. Si le cache est
utilisé dans un environnement JTA, vous devez spécifier
<literal>hibernate.transaction.manager_lookup_class</literal>. Dans d'autres
environnements, vous devriez vous assurer que la transation est terminée à l'appel
de <literal>Session.close()</literal> ou <literal>Session.disconnect()</literal>
</para>
</sect2>
<sect2 id="performance-cache-transactional">
<title>Stratégie : transactionelle</title>
<para>
La stratégie de cache <literal>transactional</literal> supporte un cache
complètement transactionnel comme, par exemple, JBoss TreeCache. Un tel cache ne
peut être utilisé que dans un environnement JTA et vous devez spécifier
<literal>hibernate.transaction.manager_lookup_class</literal>.
</para>
</sect2>
<para>
Aucun des caches livrés ne supporte toutes les stratégies de concurrence. Le tableau suivant montre
quels caches sont compatibles avec quelles stratégies de concurrence.
</para>
<table frame="topbot">
<title>Straté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-écriture non stricte)</entry>
<entry>read-write (lecture-é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érer les caches</title>
<para>
A chaque fois que vous passez un objet à la méthode <literal>save()</literal>,
<literal>update()</literal> ou <literal>saveOrUpdate()</literal> et à chaque fois
que vous récupérez un objet avec <literal>load()</literal>, <literal>get()</literal>,
<literal>list()</literal>, <literal>iterate()</literal> or <literal>scroll()</literal>,
cet objet est ajouté au cache interne de la <literal>Session</literal>.
</para>
<para>
Lorsqu'il y a un appel à la méthode <literal>flush()</literal>, l'état de cet objet
va être synchronisé avec la base de donné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è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è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ôle comme une session particulière interragit avec le
cache de second niveau
</para>
<itemizedlist>
<listitem>
<para>
<literal>CacheMode.NORMAL</literal> - lit et é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 écrit pas sauf dans le cache d'une mise à jour d'une donnée
</para>
</listitem>
<listitem>
<para>
<literal>CacheMode.PUT</literal> - é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> - é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çant un rafraî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ée au requê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 à conserver les entrées dans le
cache sous un format plus compré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êtes</title>
<para>
Les résultats d'une requête peuvent aussi être placés en cache. Ceci n'est utile
que pour les requêtes qui sont exécutées avec les mêmes paramètres. Pour utiliser
le cache de requêtes, vous devez d'abord l'activer :
</para>
<programlisting><![CDATA[hibernate.cache.use_query_cache true]]></programlisting>
<para>
Ce paramètre amène la création de deux nouvelles régions dans le cache, une qui va conserver
le résultat des requêtes mises en cache (<literal>org.hibernate.cache.StandardQueryCache</literal>)
et l'autre qui va conserver l'horodatage des mises à jour les plus récentes effectuées sur les
tables requêtables (<literal>org.hibernate.cache.UpdateTimestampsCache</literal>).
Il faut noter que le cache de requête ne conserve pas l'état des entités, il met en cache
uniquement les valeurs de l'identifiant et les valeurs de types de base (?). Le cache
de requête doit toujours être utilisé avec le cache de second niveau pour être efficace.
</para>
<para>
La plupart des requêtes ne retirent pas de bénéfice pas du cache,
donc par défaut les requê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ête est exécutée.
</para>
<para>
Si vous avez besoin de contrôler finement les délais d'expiration du cache, vous
pouvez spécifier une région de cache nommée pour une requête particuliè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ête doit forcer le rafraîchissement de sa région de cache, vous devez
appeler <literal>Query.setCacheMode(CacheMode.REFRESH)</literal>. C'est particulièrement
utile lorsque les données peuvent avoir été mises à jour par un processus séparé (e.g. elles
n'ont pas été modifiées par Hibernate). Cela permet à l'application de rafraîchir de
manière sélective les résultats d'une requête particulière. Il s'agit d'une alternative plus
efficace à l'éviction d'une région du cache à 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à passé du temps à discuter des collections.
Dans cette section, nous allons traiter du comportement des
collections à l'exé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érentes relations entre les tables
et les clés étrangères mais ne nous apprend rien de ce que nous devons savoir
sur le modèle relationnel. Pour comprendre parfaitement la structure relationnelle
et les caractéristiques des performances, nous devons considérer la structure
de la clé primaire qui est utilisée par Hibernate pour mettre à jour ou supprimer
les éléments des collections. Celà nous amène aux classifications suivantes :
</para>
<itemizedlist>
<listitem>
<para>collections indexées</para>
</listitem>
<listitem>
<para>sets</para>
</listitem>
<listitem>
<para>bags</para>
</listitem>
</itemizedlist>
<para>
Toutes les collections indexées (maps, lists, arrays) ont une clé primaire constituée
des colonnes clé (<literal>&lt;key&gt;</literal>) et <literal>&lt;index&gt;</literal>.
Avec ce type de clé primaire, la mise à jour de collection est en général très performante - la clé
primaire peut être indexées efficacement et un élément particulier peut être
localisé efficacement lorsqu'Hibernate essaie de le mettre à jour ou de le supprimer.
</para>
<para>
Les Sets ont une clé primaire composée de <literal>&lt;key&gt;</literal> et des
colonnes représentant l'élément. Elle est donc moins efficace pour certains
types de collections d'éléments, en particulier les éléments composites,
les textes volumineux ou les champs binaires ; la base de données
peut ne pas être capable d'indexer aussi efficacement une clé primaire
aussi complexe. Cependant, pour les associations un-vers-plusieurs
ou plusieurs-vers-plusieurs, spécialement lorsque l'on utilise des entités
ayant des identifiants techniques, il est probable que cela soit aussi efficace
(note : si vous voulez que <literal>SchemaExport</literal> créé effectivement
la clé 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 à l'aide d'<literal>&lt;idbag&gt;</literal> définit une clé
de substitution ce qui leur permet d'être très efficaces lors de la
mise à jour. En fait il s'agit du meilleur cas de mise à jour d'une collection
</para>
<para>
Le pire cas intervient pour les Bags. Dans la mesure où un bag permet
la duplications des éléments et n'a pas de colonne d'index, aucune clé primaire
ne peut être définie. Hibernate n'a aucun moyen de distinguer des enregistrements
dupliqués. Hibernate résout ce problème en supprimant complètement les
enregistrements (via un simple <literal>DELETE</literal>), puis en recréant
la collection chaque fois qu'elle change. Ce qui peut être très inefficace.
</para>
<para>
Notez que pour une relation un-vers-plusieurs, la "clé primaire"
peut ne pas être la clé primaire de la table en base de donné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 à jour</title>
<para>
La discussion précédente montre clairement que les collections indexées
et (la plupart du temps) les sets, permettent de réaliser le plus efficacement
les opérations d'ajout, de suppression ou de modification d'éléments.
</para>
<para>
Il existe un autre avantage qu'ont les collections indexé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érente d'un <literal>Set</literal>, Hibernate n'effectue jamais
d'<literal>UPDATE</literal> quand un enregistrement est modifié. Les modifications
apportées à 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ès s'être rappelé que les tableaux ne peuvent pas être chargés tardivement,
nous pouvons conclure que les lists, les maps et les idbags sont les types de collections
(non inversées) les plus performants, avec les sets pas loin derriè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èle
relationnel.
</para>
<para>
Cependant, dans des modèles objet bien conç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 à jour sont gérées
au niveau de l'association "plusieurs-vers-un" et les considérations de performance de
mise à 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écis où les bags
(et les lists) sont bien plus performants que les sets. Pour une collection marquée
comme <literal>inverse="true"</literal> (le choix le plus courant pour un relation
un vers plusieurs bidirectionnelle), nous pouvons ajouter des éléments à un bag
ou une list sans avoir besoin de l'initialiser (fetch) les élé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 éléments d'une collection un par un peut être extrêmement inefficace.
Hibernate n'est pas totalement stupide, il sait qu'il ne faut pas le faire dans le cas d'une
collection complètement vidé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 élément dans une collection de taille vingt et que nous
enlevions ensuite deux éléments. Hibernate effectuera un <literal>INSERT</literal> puis
deux <literal>DELETE</literal> (à moins que la collection ne soit un bag). Ce qui est
souhaitable.
</para>
<para>
Cependant, supposons que nous enlevions dix huit éléments, laissant ainsi deux éléments, puis
que nous ajoutions trois nouveaux éléments. Il y a deux moyens de procéder.
</para>
<itemizedlist>
<listitem>
<para>effacer dix huit enregistrements un à un puis en insérer trois</para>
</listitem>
<listitem>
<para>effacer la totalité de la collection (en un <literal>DELETE</literal> SQL) puis insérer
les cinq éléments restant un à 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ôt heureux qu'Hibernate ne soit pas trop intelligent ; un tel comportement
pourrait rendre l'utilisation de triggers de bases de données plutôt aléatoire, etc...).
</para>
<para>
Heureusement, vous pouvez forcer ce comportement lorsque vous le souhaitez, en liberant
(c'est-à-dire en déréférençant) la collection initiale et en retournant une collection
nouvellement instanciée avec les éléments restants. Ceci peut être très pratique et
trè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é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érêt sans le suivi et l'accès aux données de
performance. Hibernate fournit toute une panoplie de rapport sur ses opé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éder au métriques d'une <literal>SessionFactory</literal> de deux
manières. La premiè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 é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è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> à <literal>false</literal>
</para>
</listitem>
</itemizedlist>
<itemizedlist>
<listitem>
<para>
à chaud avec <literal>sf.getStatistics().setStatisticsEnabled(true)</literal>
ou <literal>hibernateStatsBean.setStatisticsEnabled(true)</literal>
</para>
</listitem>
</itemizedlist>
<para>
Les statistiques peuvent être remises à zéro de manière programmatique à l'aide de la méthode
<literal>clear()</literal>
Un résumé peut être envoyé à un logger (niveau info) à l'aide de la méthode <literal>logSummary()</literal>
</para>
</sect2>
<sect2 id="performance-monitoring-metrics" revision="1">
<title>Métriques</title>
<para>
Hibernate fournit un certain nombre de métriques, qui vont des informations très basiques
aux informations très spécialisées qui ne sont appropriées que dans certains scenarii.
Tous les compteurs accessibles sont décrits dans l'API de l'interface
<literal>Statistics</literal> dans trois catégories :
</para>
<itemizedlist>
<listitem>
<para>
Les métriques relatives à l'usage général de la <literal>Session</literal>
comme le nombre de sessions ouvertes, le nombre de connexions JDBC récupérées, etc...
</para>
</listitem>
<listitem>
<para>
Les métriques relatives aux entités, collections, requêtes et caches dans
leur ensemble (métriques globales),
</para>
</listitem>
<listitem>
<para>
Les métriques détaillées relatives à une entité, une collection, une requête
ou une région de cache particulière.
</para>
</listitem>
</itemizedlist>
<para>
Par exemple, vous pouvez vérifier l'accès au cache ainsi que le taux d'éléments manquants et
de mise à jour des entités, collections et requêtes et le temps moyen que met une requête.
Il faut faire attention au fait que le nombre de millisecondes est sujet à approximation en
Java. Hibernate est lié à la précision de la machine virtuelle, sur certaines plateformes,
cela n'offre qu'une précision de l'ordre de 10 secondes.
</para>
<para>
Des accesseurs simples sont utilisés pour accéder aux métriques globales (e.g. celles qui ne
sont pas liées à une entité, collection ou région de cache particulière). Vous pouvez accéder
aux métriques d'une entité, collection, région de cache particulière à l'aide de son nom et à l'aide
de sa représentation HQL ou SQL pour une requête. Référez vous à 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és, collections, requêtes et régions de cache, vous pouvez
récupérer la liste des noms des entités, collections, requê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>