<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Le Touilleur Express &#187; hibernate</title>
	<atom:link href="http://www.touilleur-express.fr/tag/hibernate/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.touilleur-express.fr</link>
	<description>Blog sur Java, le métier de développeur et la vie de freelance par Nicolas Martignole</description>
	<lastBuildDate>Wed, 08 Feb 2012 11:54:37 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Jazoon day 3 : Hibernate Search</title>
		<link>http://www.touilleur-express.fr/2009/06/25/jazoon-day-3-hibernate-search/</link>
		<comments>http://www.touilleur-express.fr/2009/06/25/jazoon-day-3-hibernate-search/#comments</comments>
		<pubDate>Thu, 25 Jun 2009 15:12:31 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[hibernate]]></category>
		<category><![CDATA[jazoon]]></category>

		<guid isPermaLink="false">http://www.touilleur-express.fr/?p=1612</guid>
		<description><![CDATA[J&#8217;ai assisté à la présentation d&#8217;Hibernate Search par Emmanuel Bernard, après une présentation sur les Portlets 2.0 par Thomas Heute. Tous les deux de JBoss/Red Hat.
L&#8217;objectif est de nous présenter le fonctionnement d&#8217;Hibernate Search à travers des exemples de codes. Emmanuel assure la présentation avec un ordinateur de secours, son portable n&#8217;ayant pas supporté le voyage. Ouch ! Armé du package du bon speaker : un Mac + IDEA IntelliJ, il débute la présentation par une courte introduction afin de se présenter. Membre de l&#8217;équipe d&#8217;Hibernate, JCP Specification Leader de ...]]></description>
			<content:encoded><![CDATA[<p>J&#8217;ai assisté à la présentation d&#8217;Hibernate Search par Emmanuel Bernard, après une présentation sur les Portlets 2.0 par Thomas Heute. Tous les deux de JBoss/Red Hat.</p>
<p>L&#8217;objectif est de nous présenter le fonctionnement d&#8217;Hibernate Search à travers des exemples de codes. Emmanuel assure la présentation avec un ordinateur de secours, son portable n&#8217;ayant pas supporté le voyage. Ouch ! Armé du package du bon speaker : un Mac + IDEA IntelliJ, il débute la présentation par une courte introduction afin de se présenter. Membre de l&#8217;équipe d&#8217;Hibernate, JCP Specification Leader de la JSR-303 &laquo;&nbsp;Bean Validation&nbsp;&raquo; et membre de l&#8217;expert group sur JPA 2.0. Il est l&#8217;auteur du livre &laquo;&nbsp;<a href="http://www.manning.com/bernard/">Hibernate Search In Action</a>&nbsp;&raquo; chez Mannings. Enfin petite information, il revient vivre en France en octobre, ce qui permettra je l&#8217;espère de le voir lors des soirées des Java User Group en France.</p>
<p>Son objectif sera de nous expliquer en quoi consiste la recherche &laquo;&nbsp;full-text&nbsp;&raquo;, ce qu&#8217;elle peut nous apporter, la magie des analyseurs, les différents algorithmes pour la recherche approximative, bref comment Hibernate Search fonctionne.</p>
<p><strong>La recherche</strong><br />
Que ce soit un formulaire sur un site web ou autre, tôt ou tard une application souhaite offrir une fonction de recherche dans les entités d&#8217;une base de données. Hibernate Search vise à offrir une solution efficace et puissante, basée sur Lucene, afin de nous assister lors de l&#8217;écriture de cette fonctionnalité clé dans les applications.</p>
<p>Une recherche simple basée sur la syntaxe SQL est trop restrictive. Si vous tapez &laquo;&nbsp;car&nbsp;&raquo;, comment extraire ce mot d&#8217;une colonne ? Certes il y a des requêtes comme &laquo;&nbsp;LIKE&nbsp;&raquo; mais celles-ci coûtent chères en terme de performance. Il explique que la base de données est le premier point de contention, qu&#8217;il est donc dommage de ne pas remonter cette fonction afin de la rendre plus puissante.</p>
<p>Par ailleurs, si vous tapez &laquo;&nbsp;car&nbsp;&raquo;, est-ce que le moteur de recherche sera capable de vous montrer les entrées avec &laquo;&nbsp;vehicle&nbsp;&raquo; ? Et si vous faites une faute de frappe, est-ce que le moteur sera capable de vous monter un résultat ?</p>
<p>La sélection par SQL est donc très limitée.</p>
<p>Emmanuel enchaîne ensuite avec un argument : comment classer par pertinence les résultats ? De ces besoins est né l&#8217;idée d&#8217;Hibernate Search, offrir un moteur de recherche full-text à Hibernate.</p>
<p> <strong>Full Text Search</strong><br />
Pour répondre aux besoins exposés précédemment, la solution s&#8217;appelle &laquo;&nbsp;Full Text Search&nbsp;&raquo;. Elle est relativement simple et puissante. Le principe est de créer un Index des mots clés, en supprimant les mots de liaisons, afin d&#8217;identifier les occurences de chaque mot, et donc de créer de la pertinence. Ce principe est très performant puisque l&#8217;on ne fait pas d&#8217;accès à la base de données.</p>
<p>Il existe déjà différentes solutions, soit embarquées dans la base, soit dans des boîtiers externes sous la forme d&#8217;applicance, mais qui manquent de flexibilité, puisqu&#8217;en tant que développeur il sera difficile d&#8217;affiner la stratégie d&#8217;indexation.</p>
<p><strong>Exemples de problèmes</strong><br />
Voici la mission d&#8217;Hibernate Search :<br />
 &#8211; Trouver le meilleur document, la meilleure entité selon son critère de choix.<br />
 &#8211; Classer par pertinence<br />
 &#8211; Utiliser un algorithme de similarité pour proposer à l&#8217;utilisateur des résultats similaires</p>
<p><strong>Extraction</strong><br />
L&#8217;indexation s&#8217;effectue tout d&#8217;abord en découpant les phrases en mots, ce que l&#8217;on appelle l&#8217;extraction. A ce propos, la construction de l&#8217;index est gérée par Hibernate Search, elle s&#8217;effectue automatiquement.<br />
L&#8217;extraction consiste tout d&#8217;abord à découper les phrases et à filtrer les mots communs pour ne garder que le sens.</p>
<p><strong>Approximation</strong><br />
Ce système permet d&#8217;assister l&#8217;utilisateur en lui proposant des résultats très proches lorsqu&#8217;une recherche exacte ne retourne pas de résultat par exemple. Il est possible de dire à Hibernate Search que l&#8217;approximation est moins importante que la recherche exacte, de sorte que la liste des résultats ne soit pas polluée et reste pertinente. Ce poids lors de la recherche est configuré par le développeur, ce qui nous permet de régler finement le comportement du moteur. Les réponses exactes apparaissent en premier, puis les approximations.<br />
La résolution d&#8217;approximation se fait avec des algos comme le calcul de distance Levenshtein. Tapez par exemple Hib<strong>re</strong>nate au lieu d&#8217;Hibernate, et vous verrez que le moteur détecte les résultats proches, en calculant la distance entre les lettres.</p>
<p><strong>L&#8217;approche n-gram</strong><br />
Emmanuel présente ensuite une fonction plus avancée d&#8217;indexation, appelée n-gram. Le principe consiste à découper des mots en paquets de lettre. Par exemplte un tri-gram pour découper en paquet de 3 lettres un mot comme Hibernate donnerait : hib-ern-ate. Cela permet alors de créer des arbres d&#8217;indexation et de donner des plans de résultats encore plus pertinent.</p>
<p>Emmanuel montre un exemple où il tappe &laquo;&nbsp;poter&nbsp;&raquo;. les livres sur Harry Potter s&#8217;affichent, mais aussi un<br />
&laquo;&nbsp;Capote&nbsp;&raquo; car le tri-gram &laquo;&nbsp;pot&nbsp;&raquo; a matché <img src='http://www.touilleur-express.fr/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> </p>
<p><strong>Hibernate Search</strong><br />
<em>je mettrai des photos en ligne un peu plus tard avec des exemples de code</em></p>
<p>Emmanuel prend ensuite l&#8217;exemple d&#8217;un Item pour nous présenter l&#8217;API.<br />
On voit une annotation @Indexed placée sur un Entity, ce qui permet à Hibernate Search de savoir que cette entité doit être indexée. Par ailleurs il explique qu&#8217;Hibernate Search sait que lorsque l&#8217;objet est changé, il doit mettre à jour l&#8217;index Lucène. Il nous montre enfin comment configurer l&#8217;indexation afin de ne pas découper en n-gram par exemple certains champs, afin de conserver des codes ISIN.</p>
<p>La recherche s&#8217;effectue comme une requête HQL avec un entitymanager particulier, le FullTextEntityManager qui est une sous-classe de l&#8217;EntityManager d&#8217;Hibernate.</p>
<pre>
FullTextQuery q = entityManager.createFullTextQuery(luceneQuery, Item.class)
</pre>
<p>Pour construire sa requête Lucene, Hibernate Search laisse le soin au développeur de construire celle-ci, ce qui permet de régler finement le type de recherche. Il commence par faire une démonstration d&#8217;une recherche exacte, puis ensuite une démonstration d&#8217;une recherche avec un n-gram Analyzer.</p>
<p>Il est aussi possible de déclarer par exemple 2 stratégies d&#8217;indexation différentes, une indexation exacte et une indexation pour une recherche approximative. Vraiment intéressant de voir la possibilité de réglage d&#8217;Hibernate Search. La quantité d&#8217;annotations peut faire peur, mais le code est vraiment simple à lire. Quelqu&#8217;un demande d&#8217;ailleurs s&#8217;il serait possible de déclarer dans un fichier XML ce qui est déclaré ici sous forme d&#8217;Annotations.</p>
<p><strong>Approximation phonetique</strong><br />
Il existe différents algos pour indexer de manière phonétique des mots:<br />
- Soundex<br />
- Metaphone (JRSKP)<br />
- mostly for latin language<br />
Emmanuel explique cependant que ce n&#8217;est pas le cas le plus courant dans la vraie vie.</p>
<p><strong>La recherche par synonyme</strong><br />
L&#8217;ídée est de proposer à l&#8217;utilisateur des listes de résultats dont le domaine est proche sémantiquement, une voiture, une bagnole, un caisse si vous voulez. Pour cela il faut un dictionnaire, que l&#8217;on indexe. Cela crée de gros indexes, mais il est possible de construire des dictionnaires par référence. Dans tous les cas, il nous conseille de construire nous-mêmes nos bases de synonymes.</p>
<p><strong>What&#8217;s the catch</strong><br />
Lucene étant relativement bas niveau, l&#8217;intégration avec un modèle, la création et la mise à jour de l&#8217;index sont donc des fonctions clés d&#8217;Hibernate Search. Celui-ci permet d&#8217;apporter facilement des fonctions de recherches très puissantes à une application. Et qui n&#8217;offrira pas finalement une fonctionnalité de recherche ?</p>
<p>Bonne présentation, détendue, claire, et intéressante. Et en plus avec un Mac et IDEA IntelliJ !</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2009/06/25/jazoon-day-3-hibernate-search/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hibernate : le chargement tardif c&#039;est extra</title>
		<link>http://www.touilleur-express.fr/2009/03/09/hibernate-le-chargement-tardif-cest-extra/</link>
		<comments>http://www.touilleur-express.fr/2009/03/09/hibernate-le-chargement-tardif-cest-extra/#comments</comments>
		<pubDate>Mon, 09 Mar 2009 21:42:50 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[hibernate]]></category>

		<guid isPermaLink="false">http://www.touilleur-express.fr/?p=849</guid>
		<description><![CDATA[Voici la suite de mon article précédent sur Hibernate et le chargement des associations.
Il existe 3 paramètres possible pour l&#8217;élément lazy lors de la définition d&#8217;une association dans une entité :
 &#8211; false
 &#8211; true
 &#8211; extra

...
        
            
            
        
...

Le test est simple : j&#8217;édite le fichier Item.hbm.xml et j&#8217;active le chargement tardif ...]]></description>
			<content:encoded><![CDATA[<p>Voici la suite de <a href="http://www.touilleur-express.fr/2009/03/03/hibernate-gerer-le-chargement-des-associations-efficacement/">mon article précédent</a> sur Hibernate et le chargement des associations.</p>
<p>Il existe 3 paramètres possible pour l&#8217;élément lazy lors de la définition d&#8217;une association dans une entité :<br />
 &#8211; false<br />
 &#8211; true<br />
 &#8211; extra</p>
<pre name="code" class="xml">
...
        <set name="bids" table="ITEM_BIDS" lazy="false|true|extra">
            <key column="ITEM_ID"/>
            <one -to-many class="org.touilleur.hibernate.v1.Bid"/>
        </set>
...
</pre>
<p>Le test est simple : j&#8217;édite le fichier Item.hbm.xml et j&#8217;active le chargement tardif (lazy=true). Le code Java affiche le nom de l&#8217;item chargé puis ensuite le nombre d&#8217;enchères sur l&#8217;Item en utilisant la méthode size() sur le HashSet.</p>
<p><strong>Code Java</strong></p>
<pre name="code" class="java">
        long start = System.currentTimeMillis();
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        item1 = (Item) session.load(Item.class, new Integer(id));
        logger.info("Item name is ["+item1.getName()+"]");
        logger.info("Size of Bids Set : "+ item1.getBids().size())  ;
        logger.info("Ellapsed: " + (System.currentTimeMillis() - start));
</pre>
<p><strong>Résultats de l&#8217;exécution</strong></p>
<pre>
   Hibernate: /* load org.touilleur.hibernate.v1.Item */
            select item0_.ITEM_ID as ITEM1_0_0_, item0_.name as name0_0_, item0_.price as price0_0_
            from ITEM item0_ where item0_.ITEM_ID=?
INFO 2009-03-09 22:12:20,234 [Demo Hibernate] : Item name is [Item 1]
   Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */
            select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_,
                      bids0_.BID_ID as BID1_1_0_,
                      bids0_.amount as amount1_0_, bids0_.date as date1_0_,
                      bids0_.ITEM_ID as ITEM4_1_0_
           from BID bids0_ where bids0_.ITEM_ID=?
INFO 2009-03-09 22:12:20,244 [Demo Hibernate] : Size of Bids Set : 10
INFO 2009-03-09 22:12:20,244 [Demo Hibernate] : Ellapsed: 23
</pre>
<p>On constate qu&#8217;Hibernate effectue tout d&#8217;abord une requête sur la table Item, affiche le nom de l&#8217;item puis ensuite effectue le chargement de l&#8217;ensemble des Bids en spécifiant l&#8217;id de l&#8217;Item. Je vois plusieurs soucis : le chargement de l&#8217;ensemble des colonnes de la table Bid, la deuxième requête, et finalement la requête SQL n&#8217;est pas franchement optimisée : nous voulons afficher la taille de la collection, or Hibernate charge l&#8217;ensemble des éléments&#8230;<br />
Ce qui veut dire que 10 objets de type Bid seront créés et que la taille du hashSet de bids sera calculée en effectuant un appel à la méthode hashSet.size().<br />
Je ne suis pas sûr d&#8217;être clair : pour retourner la taille de la collection, Hibernate charge toute la collection et retourne la taille de la liste&#8230; Vous imaginez la consommation en terme de mémoire ? Je serai presque tenté de dire qu&#8217;Hibernate n&#8217;est pas franchement écologique.</p>
<p>Voyons ce qu&#8217;il se passe lorsque le chargement tardif est désactivé en mettant lazy=false dans l&#8217;attribut set, dans le fichier Item.hbm.xml</p>
<pre>
INFO 2009-03-09 22:17:20,756 [Demo Hibernate] : testLazyFalse
   Hibernate: /* load org.touilleur.hibernate.v1.Item */
            select item0_.ITEM_ID as ITEM1_0_0_, item0_.name as name0_0_,
                      item0_.price as price0_0_ from ITEM item0_
            where item0_.ITEM_ID=?
   Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */
            select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_,
                      bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_,
                     bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_
            from BID bids0_
            where bids0_.ITEM_ID=?
INFO 2009-03-09 22:17:20,777 [Demo Hibernate] : Item name is [Item 1]
INFO 2009-03-09 22:17:20,778 [Demo Hibernate] : Size of Bids Set : 10
INFO 2009-03-09 22:17:20,778 [Demo Hibernate] : Ellapsed: 21
</pre>
<p>Nous constatons que la seule différence, c&#8217;est qu&#8217;Hibernate effectue toutes les requêtes avant l&#8217;exécution du code&#8230;<br />
Mais encore une fois, si notre code se contentait d&#8217;afficer par exemple le nombre d&#8217;enchères (ici 10) nous aurions chargé encore une fois l&#8217;arbre d&#8217;enchères pour rien. Bref pas d&#8217;optimisation en vue pour l&#8217;instant.</p>
<p>Voyons pour terminer ce qu&#8217;Hibernate fait lorsqe le paramètre lazy est mis à <strong>extra</strong> :</p>
<pre name="code" class="xml">
...
        <set name="bids" table="ITEM_BIDS" lazy="extra">
            <key column="ITEM_ID"/>
            <one -to-many class="org.touilleur.hibernate.v1.Bid"/>
        </set>
...
</pre>
<p>L&#8217;exécution nous donne le résultat suivant:</p>
<pre>
   Hibernate: /* load org.touilleur.hibernate.v1.Item */
          select item0_.ITEM_ID as ITEM1_0_0_, item0_.name as name0_0_,
                    item0_.price as price0_0_
          from ITEM item0_
          where item0_.ITEM_ID=?
INFO 2009-03-09 22:25:10,702 [Demo Hibernate] : Item name is [Item 1]
   Hibernate:
             select count(BID_ID) from BID where ITEM_ID =?
INFO 2009-03-09 22:25:10,707 [Demo Hibernate] : Size of Bids Set : 10
INFO 2009-03-09 22:25:10,707 [Demo Hibernate] : Ellapsed: 17
</pre>
<p>Cette fois-ci Hibernate utilise un SELECT COUNT pour calculer la taille de la collection <strong>sans</strong> la charger, ce qui est d&#8217;une part plus rapide, et d&#8217;autre part plus économique en terme de données chargées et d&#8217;instance préparées.</p>
<p>La valeur <strong>lazy</strong> est un moyen d&#8217;indiquer à Hibernate que l&#8217;objet Père (ici Item) contient une collection d&#8217;enfants (Bids) très grande. Si votre application calcule la taille du Set avec la méthode size() ou que vous testez par exemple si la collection est vide avec getBids().isEmpty(),  Hibernate effectue une requête de type SELECT COUNT, ce qui revient donc à éviter de charger le contenu des enchères.</p>
<p>C&#8217;est donc une optimisation possible dans une application afin d&#8217;éviter de charger un arbre d&#8217;objet trop important dans votre code.</p>
<p>Si vous utilisez les annotations voici comment déclarer ce paramère :</p>
<pre name="code" class="java">

// Code de la classe Item
@OneToMany
@org.hibernate.annoations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.EXTRA)
private Set bids = new HashSet();
</pre>
<p>Voilà pour la deuxième partie, j&#8217;ai encore quelques idées que je vous proposerai dans d&#8217;autres articles prochainement.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2009/03/09/hibernate-le-chargement-tardif-cest-extra/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hibernate : gérer le chargement des associations efficacement</title>
		<link>http://www.touilleur-express.fr/2009/03/03/hibernate-gerer-le-chargement-des-associations-efficacement/</link>
		<comments>http://www.touilleur-express.fr/2009/03/03/hibernate-gerer-le-chargement-des-associations-efficacement/#comments</comments>
		<pubDate>Tue, 03 Mar 2009 14:17:25 +0000</pubDate>
		<dc:creator>Nicolas Martignole</dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[hibernate]]></category>

		<guid isPermaLink="false">http://www.touilleur-express.fr/?p=800</guid>
		<description><![CDATA[Hibernate en quelques mots
Hibernate est un moteur de mapping object-relationnel (ORM) qui permet de charger des données venant d&#8217;une base de données, vers le monde Java en réduisant la quantité de code Java à écrire.
Vous définissez une classe Java, ensuite un fichier de mapping au format XML, et Hibernate offre alors un moyen pour charger, modifier et effacer vos données. Il dispose aussi d&#8217;un cache de requête afin d&#8217;optimiser les appels et d&#8217;éviter des aller-retours inutiles avec la base de données. Un cache de second niveau offre un moyen très ...]]></description>
			<content:encoded><![CDATA[<h3>Hibernate en quelques mots</h3>
<p>Hibernate est un moteur de mapping object-relationnel (ORM) qui permet de charger des données venant d&#8217;une base de données, vers le monde Java en réduisant la quantité de code Java à écrire.<br />
Vous définissez une classe Java, ensuite un fichier de mapping au format XML, et Hibernate offre alors un moyen pour charger, modifier et effacer vos données. Il dispose aussi d&#8217;un cache de requête afin d&#8217;optimiser les appels et d&#8217;éviter des aller-retours inutiles avec la base de données. Un cache de second niveau offre un moyen très puissant mais délicat à maîtriser pour partager vos données et réduire le coût de la base de données dans une application.<br />
Il est possible d&#8217;utiliser soit des annotations sur la classe Java, soit des fichiers XML. Sachez que si vous utilisez des annotations, un fichier de configuration XML peut vous permettre de surcharger le mapping des annotations, et donc d&#8217;éviter d&#8217;être bloqué par du code déjà compilé sur lequel vous n&#8217;avez pas le contrôle.</p>
<h2>Objectifs de l&#8217;article</h2>
<p>La première partie de l&#8217;article est assez dictatique pour ceux qui ne connaissent pas Hibernate 3. La seconde partie sera plus intéressante pour les utilisateurs qui souhaitent voir quelques options avancées d&#8217;Hibernate. Nous verrons comment gérer efficacement les relations entre les objets, les stratégies de chargement et les différentes types d&#8217;option de chargement tardif (Lazy instanciation).</p>
<h2>Code source de l&#8217;article</h2>
<p>Pour les besoins de cet article j&#8217;ai préparé un projet simple avec maven2, que vous pouvez télécharger en fin d&#8217;article. Le code est distribué sous licence Creatives Commons v2 avec quelques restrictions sur la non modification et l&#8217;interdiction d&#8217;utiliser le code à des fins commerciales sans mon accord.</p>
<p>Si vous souhaitez tester quelques fonctionnalités en lisant l&#8217;article, vous aurez besoin de Java 5 et de Maven 2.0.6 minimum pour compiler et tester le code. J&#8217;utilise un test unitaire JUnit 3.8 simple pour lancer les différents tests, un simple &laquo;&nbsp;mvn test&nbsp;&raquo; en ligne de commande vous permet de compiler le code et de lancer la base de données embarquée HSQLDB.</p>
<h2>Un peu de relationnel</h2>
<p>J&#8217;ai repris un exemple simple pour passer en revue quelques caractéristiques d&#8217;Hibernate plus ou moins connues.<br />
Prenons tout d&#8217;abord la classe Item, correspondant à un article.<br />
Un Item est caractérisé par son nom, son prix et une clé d&#8217;identité. Un constructeur vide sans argument est requis pour qu&#8217;Hibernate puisse décorer cette classe avec un proxy avec la librairie CGLIB, sans quoi une exception org.hibernate.InstantiationException sera levée par Hibernate. De même il est recommandé de ne pas déclarer de classe finale comme entité si vous souhaitez qu&#8217;Hibernate puisse utiliser un proxy. Si malgré tout vous êtes obligés de déclarer une méthode finale, il faudra alors désactiver le chargement tardif (lazy=false) dans la configuration de l&#8217;entité [<a href="http://www.hibernate.org/hib_docs/v3/reference/fr-FR/html_single/#persistent-classes-pojo-final" target="_blank">1</a>]</p>
<pre class="java" name="code">
public class Item {
    private Integer id;
    private String name;
    private float price;
    private Set&lt;Bid&gt; bids=new HashSet&lt;Bid&gt;();

    public Item() {

    }

    public Item(String name, float price){
        //...
    }
     // Getter, setter, equals, hashCode et toString
}</pre>
<p>La classe Bid (enchère en anglais) est une offre émise par un acheteur sur notre Item. Les attributs sont l&#8217;heure de l&#8217;enchère et son montant.</p>
<pre class="java" name="code">
public class Bid {
    private Integer id;
    private float amount;
    private Date date;
    private Item item;

    public Bid(){
    }

    // getter, setter, hashCode, equals
}</pre>
<p>Le fichier de mapping Hibernate Item.hbm.xml permet à Hibernate d&#8217;effectuer le mapping entre la classe Item et notre base de données. La relation un-vers-plusieurs (un Item contient plusieurs Bids) est définie avec un set. Nous utiliserons une table de jointure ITEM_BIDS, le chargement tardif sera désactivé (lazy=false), nous indiquons à Hibernate que la relation inverse est déclarée dans Bid (inverse=true).</p>
<pre class="xml" name="code">
    &lt;hibernate-mapping&gt;

    &lt;class name="org.touilleur.hibernate.v1.Item" table="ITEM"&gt;
        &lt;id name="id" column="ITEM_ID"&gt;
            &lt;generator class="native"/&gt;
        &lt;/id&gt;
        &lt;property name="name"/&gt;
        &lt;property name="price"/&gt;
        &lt;set name="bids" table="ITEM_BIDS"
                                       lazy="false"
                                       inverse="true"
                                       fetch="select"
                                       cascade="all"&gt;
            &lt;key column="ITEM_ID" not-null="true"/&gt;
            &lt;one-to-many class="org.touilleur.hibernate.v1.Bid"/&gt;
        &lt;/set&gt;
    &lt;/class&gt;

&lt;/hibernate-mapping&gt;
</pre>
<p>Hibernate recommande d&#8217;utiliser des associations symétriques. Dans notre application, une enchère sans article n&#8217;a pas de sens. Nous configurons donc notre association many-to-one avec l&#8217;attribut not-null à true dans le fichier Bid.hbm.xml:</p>
<pre class="xml" name="code">
&lt;hibernate-mapping&gt;
    &lt;class name="org.touilleur.hibernate.v1.Bid" table="BID"&gt;
        &lt;id name="id" column="BID_ID"&gt;
            &lt;generator class="native"/&gt;
        &lt;/id&gt;
        &lt;property name="amount"/&gt;
        &lt;property name="date"/&gt;
        &lt;many-to-one name="item"
                               column="ITEM_ID"
                               class="org.touilleur.hibernate.v1.Item"
                               not-null="true"/&gt;
    &lt;/class&gt;

&lt;/hibernate-mapping&gt;</pre>
<h2>Stratégie de chargement de l&#8217;association</h2>
<p>Hibernate évite au maximum de faire appel à la base de données, et tend à limiter le nombre de requête, afin d&#8217;alléger le traitement. Par défaut la stratégie de chargement tardive qui permet d&#8217;optimiser les performances, peut provoquer quelques moments difficile lorsque surgit l&#8217;exception <em>LazyInitializationException</em>.</p>
<p>Une exception de type LazyInitializationException sera renvoyée par Hibernate si une collection ou un proxy non initialisé est accédé en dehors de la portée de la Session, e.g. lorsque l&#8217;entité à laquelle appartient la collection ou qui a une référence vers le proxy est dans l&#8217;état &laquo;&nbsp;détachée&nbsp;&raquo;.</p>
<p>Cela se produit dans les architectures 3-tiers, où le tiers de présentation Web est séparé physiquement de l&#8217;ejbtiers pour des raisons de sécurité. Ce type d&#8217;architecture soufre de plusieurs soucis : si vous interdisez l&#8217;accès à la base de données à partir du web-tiers, il faudra donc que la stratégie de chargement tardive d&#8217;Hibernate soit désactivée (lazy=false). Dans le cas où nous souhaitons charger un Item, toutes les enchères associées seront alors chargées. Imaginez que pour seulement afficher le nom de l&#8217;Item, nous allons aussi charger 1500 Bids par exemple&#8230;<br />
Il est alors important de configurer Hibernate finement et d&#8217;utiliser des jointures afin d&#8217;éviter trop de requêtes.</p>
<p>Dans un premier temps, nous allons regarder les différentes possibilités de chargement d&#8217;une association, en essayant différentes valeurs pour l&#8217;attribut fetch de l&#8217;attribut set. La valeur par défaut lorsqu&#8217;elle n&#8217;est pas précisée est &laquo;&nbsp;select&nbsp;&raquo;. Les 3 valeurs possibles sont <em>select, join</em> et <em>subselect</em>. Nous désactivons le chargement tardif en ajoutant le mot clé &laquo;&nbsp;lazy=false&nbsp;&raquo; dans le fichier Item.hbm.xml.</p>
<h3>Fetch strategy par défaut en mode lazy=false</h3>
<p>Pour ce test, dans le test unitaire SelectTest, nous allons voir comment il est possible de changer la stratégie de chargement directement dans le code. Avant cela, regardons comment travaille Hibernate par défaut avant de tester d&#8217;autres modes de chargement. Par défault lorsque FetchMode est à DEFAULT comme ci-dessous, Hibernate se repose sur ce que vous avez déclaré dans votre fichier de mapping, Item.hbm.xml pour nous.</p>
<pre class="java" name="code">
// voir org.touilleur.hibernate.SelectTest
 Item item1 = (Item) session.createCriteria(Item.class)
                .setFetchMode("bids", FetchMode.DEFAULT)
                .add(Restrictions.idEq(id))
                .uniqueResult();
</pre>
<p>La configuration du chargement de l&#8217;association s&#8217;effectue dans le fichier de configuration hibernate.cfg.xml à l&#8217;aide de l&#8217;attribut fetch. La valeur par défaut est select, les 2 autres valeurs possibles sont join et subselect. Voyons d&#8217;abord par défaut le comportement d&#8217;Hibernate :</p>
<pre class="xml">
       &lt;set name="bids" table="ITEM_BIDS" lazy="false" inverse="false"
                        <strong>fetch="select"</strong> cascade="all"&gt;</pre>
<p>Pour continuer, exécutez la méthode <em>testSelectOneItemWithDefault</em>.<br />
La console affiche les requêtes SQL suivantes:<br />
<code>...<br />
INFO 2009-02-27 22:24:21,788 [Demo Hibernate] : Starting selectOneItemWithDefault<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_0_, this_.name as name0_0_,<br />
    this_.price as price0_0_ from ITEM this_ where this_.ITEM_ID = ?<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_,<br />
    bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_,<br />
    bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
INFO 2009-02-27 22:24:21,791 [Demo Hibernate] : Loaded item1 with a FetchMode set to DEFAULT...</code></p>
<p>2 requêtes sont nécessaires pour effectuer le chargement de l&#8217;unique Item.</p>
<p><strong>Chargement de l&#8217;ensemble des Items</strong></p>
<p>Le test unitaire testSelecTAllItemsWithDefaultFetchMode sélectionne les 2 Items de notre base et affiche ensuite les 2 items. La méthode toString de la class Item force le chargement de la liste des enchères. Nous avons désactivé le chargement tardif (lazy=false) et il est donc normal de voir Hibernate charger les Bids.</p>
<p>Que se passe-t-il lorsque nous exécutons ce code ?<br />
<code><br />
INFO 2009-03-03 10:18:30,670 [Demo Hibernate] : testSelecTAllItemsWithDefaultFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_0_, this_.name as name0_0_, this_.price as price0_0_ from ITEM this_<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
INFO 2009-03-03 10:18:30,704 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=7, amount=107.0, date=2009-03-03 10:18:30.637}, Bid{id=10, amount=110.0, date=2009-03-03 10:18:30.643}, Bid{id=6, amount=106.0, date=2009-03-03 10:18:30.632}, Bid{id=8, amount=108.0, date=2009-03-03 10:18:30.638}, Bid{id=2, amount=102.0, date=2009-03-03 10:18:30.626}, Bid{id=4, amount=104.0, date=2009-03-03 10:18:30.629}, Bid{id=1, amount=101.0, date=2009-03-03 10:18:30.625}, Bid{id=9, amount=109.0, date=2009-03-03 10:18:30.639}, Bid{id=5, amount=105.0, date=2009-03-03 10:18:30.63}, Bid{id=3, amount=103.0, date=2009-03-03 10:18:30.628}], name='TV LCD', price=340.0}<br />
INFO 2009-03-03 10:18:30,706 [Demo Hibernate] : Item #2 : Item{id=2, bids=[Bid{id=20, amount=144.0, date=2009-03-03 10:18:30.657}, Bid{id=15, amount=125.0, date=2009-03-03 10:18:30.651}, Bid{id=13, amount=43.0, date=2009-03-03 10:18:30.647}, Bid{id=16, amount=116.0, date=2009-03-03 10:18:30.652}, Bid{id=14, amount=84.0, date=2009-03-03 10:18:30.648}, Bid{id=11, amount=10.0, date=2009-03-03 10:18:30.645}, Bid{id=17, amount=137.0, date=2009-03-03 10:18:30.654}, Bid{id=19, amount=139.0, date=2009-03-03 10:18:30.656}, Bid{id=12, amount=22.0, date=2009-03-03 10:18:30.646}, Bid{id=18, amount=138.0, date=2009-03-03 10:18:30.655}], name='Plasma', price=1230.0}<br />
INFO 2009-03-03 10:18:30,709 [Demo Hibernate] : testSelecTAllItemsWithDefaultFetchMode - end</p>
<p>Hibernate utilise 3 requêtes pour effectuer le chargement. C'est un problème important. Imaginez que votre base contient 1000 items qui eux-mêmes référencent 10 Bids différents... Nous n'optimisons pas vraiment notre chargement, cela nous coûte cher en nombre de requêtes SQL. De plus, n'étant pas en mode de chargement tardif, nous voyons bien qu'Hibernate effectue les 3 requêtes avant d'attaquer l'affichage de notre résultat.</p>
<p><strong>Que se passe-t-il lorsque le mode lazy est actif ?</strong><br />
Rappelons que par défaut Hibernate étant bien fait, ce mode est implicite et il est le mode de fonctionnement par défaut d'Hibernate. Pour ce test, je me contente de changer dans le fichier Item.hbm.xml la configuration du chargement tardif et je relance le même test afin de voir comment Hibernate charge la collection:</p>
<pre class="xml" name="code">
&lt;hibernate-mapping&gt;

    &lt;class name="org.touilleur.hibernate.v1.Item" table="ITEM"&gt;
        &lt;id name="id" column="ITEM_ID"&gt;
            &lt;generator class="native"/&gt;
        &lt;/id&gt;
        &lt;property name="name"/&gt;
        &lt;!-- The fetch join strategy uses a select join query to load with one transact the set of bids --&gt;
        &lt;property name="price"/&gt;
 &lt;-- LAZY est à TRUE --&gt;
        &lt;set name="bids" table="ITEM_BIDS" lazy="true" inverse="true" fetch="select" cascade="all"&gt;
            &lt;key column="ITEM_ID" not-null="true"/&gt;
            &lt;one-to-many class="org.touilleur.hibernate.v1.Bid"/&gt;
        &lt;/set&gt;
    &lt;/class&gt;

&lt;/hibernate-mapping&gt;</pre>
<p>Je relance le même test unitaire :<br />
</code><code><br />
INFO 2009-03-03 10:26:38,910 [Demo Hibernate] : testSelecTAllItemsWithDefaultFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_0_, this_.name as name0_0_, this_.price as price0_0_ from ITEM this_<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
INFO 2009-03-03 10:26:38,937 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=1, amount=101.0, date=2009-03-03 10:26:38.864}, Bid{id=10, amount=110.0, date=2009-03-03 10:26:38.883}, Bid{id=8, amount=108.0, date=2009-03-03 10:26:38.878}, Bid{id=3, amount=103.0, date=2009-03-03 10:26:38.867}, Bid{id=6, amount=106.0, date=2009-03-03 10:26:38.871}, Bid{id=2, amount=102.0, date=2009-03-03 10:26:38.866}, Bid{id=9, amount=109.0, date=2009-03-03 10:26:38.879}, Bid{id=5, amount=105.0, date=2009-03-03 10:26:38.87}, Bid{id=7, amount=107.0, date=2009-03-03 10:26:38.876}, Bid{id=4, amount=104.0, date=2009-03-03 10:26:38.868}], name='TV LCD', price=340.0}<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
INFO 2009-03-03 10:26:38,944 [Demo Hibernate] : Item #2 : Item{id=2, bids=[Bid{id=18, amount=138.0, date=2009-03-03 10:26:38.896}, Bid{id=11, amount=10.0, date=2009-03-03 10:26:38.885}, Bid{id=20, amount=144.0, date=2009-03-03 10:26:38.898}, Bid{id=16, amount=116.0, date=2009-03-03 10:26:38.893}, Bid{id=14, amount=84.0, date=2009-03-03 10:26:38.888}, Bid{id=19, amount=139.0, date=2009-03-03 10:26:38.897}, Bid{id=15, amount=125.0, date=2009-03-03 10:26:38.892}, Bid{id=12, amount=22.0, date=2009-03-03 10:26:38.886}, Bid{id=13, amount=43.0, date=2009-03-03 10:26:38.887}, Bid{id=17, amount=137.0, date=2009-03-03 10:26:38.895}], name='Plasma', price=1230.0}<br />
INFO 2009-03-03 10:26:38,947 [Demo Hibernate] : testSelecTAllItemsWithDefaultFetchMode - end<br />
</code></p>
<p>Cette fois-ci nous constatons qu&#8217;Hibernate effectue une requête que lorsque cela devient nécessaire, ce qui est adapté dans la majorité des cas.<br />
Lorsque le mode lazy est désactivé, Hibernate effectue n+1 requêtes sans jointure. Cela peut entraîner un très grand nombre de requêtes vers la base. Nous allons donc voir comment optimiser dans ce cas précis le chargement des associations.</p>
<h3>Fetch strategy à join et lazy=false</h3>
<p>Si nous ne pouvons pas utiliser le chargement tardif, il est alors intéressant d&#8217;utiliser une requête et d&#8217;effectuer une jointure entre la table Item et la table Bid. Pour cela il suffit de changer de stratégie lors de la requête. Nous passons maintenant le mode de récupération (FetchMode) à JOIN :</p>
<pre class="java" name="code">
// voir la class org.touilleur.hibernate.SelectTest
 Item item1 = (Item) session.createCriteria(Item.class)
                .setFetchMode("bids", FetchMode.JOIN)
                .add(Restrictions.idEq(id))
                .uniqueResult();</pre>
<p>Si vous savez qu&#8217;il sera toujours plus intéresant d&#8217;utiliser une requête par jointure vous pouvez aussi changer la stratégie dans le fichier Item.hbm.xml :<br />
<code><br />
&lt;set name="bids" table="ITEM_BIDS" lazy="false" inverse="true" <strong>fetch="join"</strong> cascade="all"&gt;<br />
</code></p>
<p>Le test selectOneItemWithJoin nous montre alors qu&#8217;une seule requête est exécutée, et qu&#8217;une jointure externe gauche entre la table Item et la table Bid permet de retrouver un Item avec 0 ou plusieurs Bid.</p>
<p><code><br />
INFO 2009-02-27 22:24:21,772 [Demo Hibernate] : Starting selectOneItemWithJoin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_1_, this_.name as name0_1_, this_.price as price0_1_,<br />
    bids2_.ITEM_ID as ITEM4_3_, bids2_.BID_ID as BID1_3_, bids2_.BID_ID as BID1_1_0_, bids2_.amount as amount1_0_,<br />
    bids2_.date as date1_0_, bids2_.ITEM_ID as ITEM4_1_0_<br />
    from ITEM this_ left outer join BID bids2_<br />
    on this_.ITEM_ID=bids2_.ITEM_ID where this_.ITEM_ID = ?<br />
INFO 2009-02-27 22:24:21,775 [Demo Hibernate] : Loaded item1 with a FetchMode set to JOIN<br />
</code><br />
Notez qu&#8217;Hibernate utilise une jointure externe de la table Item vers la table Bid, cela nous donne alors en simplifiant le code SQL la requête suivante :<br />
<code>select i.*,b* from ITEM i<br />
   outer join BID b<br />
   on i.ITEM_ID=b.ITEM_ID<br />
</code><br />
Par défaut il n&#8217;y a donc pas de critères de distinction, et nous nous retrouvons alors avec 20 résultats, ce qui sera pratique pour remplir un tableau mais qui n&#8217;est pas forcément souhaitable pour votre code.</p>
<p><code>INFO 2009-03-03 10:30:53,041 [Demo Hibernate] : testSelecTAllItemsWithJoinFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_1_, this_.name as name0_1_, this_.price as price0_1_, bids2_.ITEM_ID as ITEM4_3_, bids2_.BID_ID as BID1_3_, bids2_.BID_ID as BID1_1_0_, bids2_.amount as amount1_0_, bids2_.date as date1_0_, bids2_.ITEM_ID as ITEM4_1_0_ from ITEM this_ left outer join BID bids2_ on this_.ITEM_ID=bids2_.ITEM_ID<br />
INFO 2009-03-03 10:30:53,072 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=3, amount=103.0, date=2009-03-03 10:30:52.999}, Bid{id=8, amount=108.0, date=2009-03-03 10:30:53.01}, Bid{id=6, amount=106.0, date=2009-03-03 10:30:53.003}, Bid{id=10, amount=110.0, date=2009-03-03 10:30:53.014}, Bid{id=5, amount=105.0, date=2009-03-03 10:30:53.002}, Bid{id=2, amount=102.0, date=2009-03-03 10:30:52.998}, Bid{id=7, amount=107.0, date=2009-03-03 10:30:53.009}, Bid{id=4, amount=104.0, date=2009-03-03 10:30:53.001}, Bid{id=9, amount=109.0, date=2009-03-03 10:30:53.011}, Bid{id=1, amount=101.0, date=2009-03-03 10:30:52.997}], name='TV LCD', price=340.0}<br />
INFO 2009-03-03 10:30:53,074 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=3, amount=103.0, date=2009-03-03 10:30:52.999}, Bid{id=8, amount=108.0, date=2009-03-03 10:30:53.01}, Bid{id=6, amount=106.0, date=2009-03-03 10:30:53.003}, Bid{id=10, amount=110.0, date=2009-03-03 10:30:53.014}, Bid{id=5, amount=105.0, date=2009-03-03 10:30:53.002}, Bid{id=2, amount=102.0, date=2009-03-03 10:30:52.998}, Bid{id=7, amount=107.0, date=2009-03-03 10:30:53.009}, Bid{id=4, amount=104.0, date=2009-03-03 10:30:53.001}, Bid{id=9, amount=109.0, date=2009-03-03 10:30:53.011}, Bid{id=1, amount=101.0, date=2009-03-03 10:30:52.997}], name='TV LCD', price=340.0}<br />
...<br />
<strong>Message répété 20 fois</strong><br />
...<br />
INFO 2009-03-03 10:30:53,125 [Demo Hibernate] : testSelecTAllItemsWithJoinFetchMode - end<br />
</code><br />
Pour ajouter une clause DISTINCT, nous pouvons modifier notre critère et utiliser un ResultTransformer :</p>
<pre class="java" class="code">
List l = session.createCriteria(Item.class)
                .setFetchMode("bids", FetchMode.JOIN)
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
</pre>
<p>En exécutant à nouveau notre test voici le résultat :<br />
<code><br />
INFO 2009-03-03 10:43:31,196 [Demo Hibernate] : testSelecTAllItemsWithJoinFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_1_, this_.name as name0_1_, this_.price as price0_1_, bids2_.ITEM_ID as ITEM4_3_, bids2_.BID_ID as BID1_3_, bids2_.BID_ID as BID1_1_0_, bids2_.amount as amount1_0_, bids2_.date as date1_0_, bids2_.ITEM_ID as ITEM4_1_0_ from ITEM this_ left outer join BID bids2_ on this_.ITEM_ID=bids2_.ITEM_ID<br />
INFO 2009-03-03 10:43:31,229 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=8, amount=108.0, date=2009-03-03 10:43:31.162}, Bid{id=7, amount=107.0, date=2009-03-03 10:43:31.16}, Bid{id=2, amount=102.0, date=2009-03-03 10:43:31.149}, Bid{id=6, amount=106.0, date=2009-03-03 10:43:31.154}, Bid{id=10, amount=110.0, date=2009-03-03 10:43:31.167}, Bid{id=9, amount=109.0, date=2009-03-03 10:43:31.163}, Bid{id=3, amount=103.0, date=2009-03-03 10:43:31.15}, Bid{id=5, amount=105.0, date=2009-03-03 10:43:31.153}, Bid{id=1, amount=101.0, date=2009-03-03 10:43:31.147}, Bid{id=4, amount=104.0, date=2009-03-03 10:43:31.151}], name='TV LCD', price=340.0}<br />
INFO 2009-03-03 10:43:31,232 [Demo Hibernate] : Item #2 : Item{id=2, bids=[Bid{id=19, amount=139.0, date=2009-03-03 10:43:31.181}, Bid{id=17, amount=137.0, date=2009-03-03 10:43:31.179}, Bid{id=15, amount=125.0, date=2009-03-03 10:43:31.176}, Bid{id=13, amount=43.0, date=2009-03-03 10:43:31.171}, Bid{id=11, amount=10.0, date=2009-03-03 10:43:31.169}, Bid{id=12, amount=22.0, date=2009-03-03 10:43:31.17}, Bid{id=14, amount=84.0, date=2009-03-03 10:43:31.172}, Bid{id=20, amount=144.0, date=2009-03-03 10:43:31.182}, Bid{id=18, amount=138.0, date=2009-03-03 10:43:31.18}, Bid{id=16, amount=116.0, date=2009-03-03 10:43:31.177}], name='Plasma', price=1230.0}<br />
INFO 2009-03-03 10:43:31,235 [Demo Hibernate] : testSelecTAllItemsWithJoinFetchMode - end<br />
</code></p>
<p>Voir la FAQ Hibernate [<a href="http://www.hibernate.org/117.html#A12">Hibernate does not return distinct results for a query with outer join fetching enabled for a collection (even if I use the distinct keyword)</a>]</p>
<p>La sélection par jointure est donc une option possible. Nous sommes passés de 3 requêtes à une seule requête, ce qui est une optimisation permettant de réduire le nombre de requête SQL. Encore une fois, c&#8217;est à vous et à votre DBA de décider de la meilleur stratégie, il ne faut donc pas appliquer à l&#8217;aveugle les paramètres. Les jointures sont efficaces si vos colonnes de jointure sont correctement indexées, il est donc important de s&#8217;assurer que votre administrateur de base de données a aussi validé vos changements.</p>
<h3>Fetch mode par subselect</h3>
<p>Hibernate propose un mode de sélection moins connu et pourtant très efficace sur les associations, le mode subselect. L&#8217;idée est simplement d&#8217;utiliser le résultat d&#8217;une première requête comme critère de la sélection de la deuxième requête pour effectuer une requête plus efficace.</p>
<p>Ce que nous avons vu en premier, en mode lazy=false et fetch=select, c&#8217;est qu&#8217;Hibernate effectue n+1 requêtes pour charger la liste des Bids pour chacun des Items.</p>
<pre>
/* Retrouve la liste des Items */
select * from ITEM

/* Retrouve la list des Bids pour chaque Item */
select * from BID  where ITEM_ID = 12
select * from BID  where ITEM_ID = 34
select * from BID  where ITEM_ID = 37
select * from BID  where ITEM_ID = 39
</pre>
<p>Il y deux façons d&#8217;optimiser ce type de requête : soit utiliser le paramètre batch-size dans l&#8217;element set du fichier Item.hbm.xml, soit utiliser une requête de sous sélection. Je ne sais pas si ce mode est supporté par toutes les bases de données, mais voici le principe :<br />
- Sélectionner les Items<br />
- Sélectionner les Bids en utilisant les ID des items précedemment trouvé</p>
<pre>
/* Retrouve la liste des Items */
select * from ITEM

/* Retrouve la list des Bids pour chaque Item */
select * from BID  where ITEM_ID IN( 12, 34, 37, 39)
</pre>
<p>Pour cela il n&#8217;est pas possible de changer le mode de sélection dans le code, il faut éditer le fichier Item.hbm.xml et préciser que le fetch type est <strong>subselect</strong></p>
<pre name="code" class="xml">
&lt;hibernate-mapping&gt;
  &lt;class name="org.touilleur.hibernate.v1.Item" table="ITEM"&gt;
        &lt;id name="id" column="ITEM_ID"&gt;
            &lt;generator class="native"/&gt;
        &lt;/id&gt;
        &lt;property name="name"/&gt;
        &lt;!-- Nous testons subselect --&gt;
        &lt;property name="price"/&gt;
        &lt;set name="bids" table="ITEM_BIDS" lazy="false"
               inverse="true"
               fetch="subselect"
               cascade="all"&gt;
            &lt;key column="ITEM_ID" not-null="true"/&gt;
            &lt;one-to-many class="org.touilleur.hibernate.v1.Bid"/&gt;
        &lt;/set&gt;
    &lt;/class&gt;

&lt;/hibernate-mapping&gt;</pre>
<p>Après avoir édité le fichier Item.hbm.xml, le test unitaire testSelecTAllItemsWithSubSelectFetchMode nous montre que deux requêtes sont exécutées au lieu de n+1 dans le cas du mode &laquo;&nbsp;select&nbsp;&raquo; ou une seule requête dans le cas du mode &laquo;&nbsp;join&nbsp;&raquo; :<br />
<code><br />
INFO 2009-03-03 11:06:36,678 [Demo Hibernate] : testSelecTAllItemsWithSubSelectFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_0_, this_.name as name0_0_, this_.price as price0_0_ from ITEM this_<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */<br />
select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_,<br />
  bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_,<br />
  bids0_.ITEM_ID as ITEM4_1_0_<br />
from BID bids0_<br />
where bids0_.ITEM_ID<br />
   in (select this_.ITEM_ID from ITEM this_)<br />
INFO 2009-03-03 11:06:36,711 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=7, amount=107.0, date=2009-03-03 11:06:36.646}, Bid{id=3, amount=103.0, date=2009-03-03 11:06:36.636}, Bid{id=8, amount=108.0, date=2009-03-03 11:06:36.647}, Bid{id=2, amount=102.0, date=2009-03-03 11:06:36.635}, Bid{id=6, amount=106.0, date=2009-03-03 11:06:36.64}, Bid{id=5, amount=105.0, date=2009-03-03 11:06:36.639}, Bid{id=1, amount=101.0, date=2009-03-03 11:06:36.633}, Bid{id=9, amount=109.0, date=2009-03-03 11:06:36.648}, Bid{id=4, amount=104.0, date=2009-03-03 11:06:36.637}, Bid{id=10, amount=110.0, date=2009-03-03 11:06:36.652}], name='TV LCD', price=340.0}<br />
INFO 2009-03-03 11:06:36,714 [Demo Hibernate] : Item #2 : Item{id=2, bids=[Bid{id=14, amount=84.0, date=2009-03-03 11:06:36.657}, Bid{id=18, amount=138.0, date=2009-03-03 11:06:36.664}, Bid{id=17, amount=137.0, date=2009-03-03 11:06:36.663}, Bid{id=20, amount=144.0, date=2009-03-03 11:06:36.666}, Bid{id=15, amount=125.0, date=2009-03-03 11:06:36.66}, Bid{id=12, amount=22.0, date=2009-03-03 11:06:36.655}, Bid{id=13, amount=43.0, date=2009-03-03 11:06:36.656}, Bid{id=19, amount=139.0, date=2009-03-03 11:06:36.665}, Bid{id=16, amount=116.0, date=2009-03-03 11:06:36.661}, Bid{id=11, amount=10.0, date=2009-03-03 11:06:36.654}], name='Plasma', price=1230.0}<br />
INFO 2009-03-03 11:06:36,716 [Demo Hibernate] : testSelecTAllItemsWithSubSelectFetchMode - end</code></p>
<p>L&#8217;intérêt de ce mode de sélection est de récuperer en deux requêtes l&#8217;ensemble des Items et ensuite des Bids. Si vous faites enfin le test en mode &laquo;&nbsp;lazy=true&nbsp;&raquo; vous verrez par ailleurs qu&#8217;Hibernate n&#8217;effectue pas de chargement tardif mais qu&#8217;il charge toutes les données. Le mode de sélection en &laquo;&nbsp;SUBSELECT&nbsp;&raquo; limite à deux requêtes le nombre d&#8217;interrogation nécessaire, le tout sans jointure. C&#8217;est donc une option intéressante à tester selon la logique de votre application.</p>
<h3>Fetch mode par batch size</h3>
<p>La 4ème technique pour améliorer les chargements par lot est d&#8217;activer l&#8217;option batch-size. Afin de montrer son fonctionnement nous allons d&#8217;abord limiter la taille de chargement à 1 pour le nombre d&#8217;Item et le nombre de collection de Bid :</p>
<pre name="code" class="xml">
&lt;hibernate-mapping&gt;

    &lt;class name="org.touilleur.hibernate.v1.Item" table="ITEM" batch-size="1"&gt;
        &lt;id name="id" column="ITEM_ID"&gt;
            &lt;generator class="native"/&gt;
        &lt;/id&gt;
        &lt;property name="name"/&gt;
        &lt;!-- The fetch join strategy uses a select join query to load with one transact the set of bids --&gt;
        &lt;property name="price"/&gt;
        &lt;set name="bids" table="ITEM_BIDS"
              lazy="false"
              inverse="true"
              fetch="select"
              cascade="all"
              batch-size="1"&gt;
            &lt;key column="ITEM_ID" not-null="true"/&gt;
            &lt;one-to-many class="org.touilleur.hibernate.v1.Bid"/&gt;
        &lt;/set&gt;
    &lt;/class&gt;

&lt;/hibernate-mapping&gt;
</pre>
<p>Nous voyons qu&#8217;à l&#8217;exécution, Hibernate effectue une requete pour récuperer une liste de deux Items puis ensuite deux requêtes pour chacun des Items. En mode lazy, nous aurions simplement vu que la deuxième requête de chargement aurait été effectuée plus tardivement</p>
<p><code><br />
INFO 2009-03-03 13:19:19,261 [Demo Hibernate] : testSelecTAllItemsWithSubSelectFetchMode - begin<br />
Hibernate: /* criteria query */ select this_.ITEM_ID as ITEM1_0_0_, this_.name as name0_0_, this_.price as price0_0_ from ITEM this_<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
Hibernate: /* load one-to-many org.touilleur.hibernate.v1.Item.bids */ select bids0_.ITEM_ID as ITEM4_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.amount as amount1_0_, bids0_.date as date1_0_, bids0_.ITEM_ID as ITEM4_1_0_ from BID bids0_ where bids0_.ITEM_ID=?<br />
INFO 2009-03-03 13:19:19,294 [Demo Hibernate] : Item #1 : Item{id=1, bids=[Bid{id=2, amount=102.0, date=2009-03-03 13:19:19.217}, Bid{id=5, amount=105.0, date=2009-03-03 13:19:19.221}, Bid{id=3, amount=103.0, date=2009-03-03 13:19:19.218}, Bid{id=10, amount=110.0, date=2009-03-03 13:19:19.233}, Bid{id=7, amount=107.0, date=2009-03-03 13:19:19.227}, Bid{id=4, amount=104.0, date=2009-03-03 13:19:19.219}, Bid{id=1, amount=101.0, date=2009-03-03 13:19:19.215}, Bid{id=9, amount=109.0, date=2009-03-03 13:19:19.23}, Bid{id=8, amount=108.0, date=2009-03-03 13:19:19.229}, Bid{id=6, amount=106.0, date=2009-03-03 13:19:19.222}], name='TV LCD', price=340.0}<br />
INFO 2009-03-03 13:19:19,297 [Demo Hibernate] : Item #2 : Item{id=2, bids=[Bid{id=19, amount=139.0, date=2009-03-03 13:19:19.247}, Bid{id=16, amount=116.0, date=2009-03-03 13:19:19.243}, Bid{id=11, amount=10.0, date=2009-03-03 13:19:19.236}, Bid{id=18, amount=138.0, date=2009-03-03 13:19:19.246}, Bid{id=13, amount=43.0, date=2009-03-03 13:19:19.237}, Bid{id=20, amount=144.0, date=2009-03-03 13:19:19.248}, Bid{id=14, amount=84.0, date=2009-03-03 13:19:19.238}, Bid{id=15, amount=125.0, date=2009-03-03 13:19:19.242}, Bid{id=17, amount=137.0, date=2009-03-03 13:19:19.245}, Bid{id=12, amount=22.0, date=2009-03-03 13:19:19.236}], name='Plasma', price=1230.0}<br />
INFO 2009-03-03 13:19:19,300 [Demo Hibernate] : testSelecTAllItemsWithSubSelectFetchMode - end<br />
</code></p>
<p>Le chargement par lot est une technique efficace qui permet de précharger un nombre défini de proxy non initialisé si un premier proxy est accedé. Il y a donc l&#8217;optimisation au niveau du chargement de l&#8217;entité (Item) et l&#8217;optimisation au niveau du chargement de la collection (Bid).</p>
<p>Le chargement par lot pour notre classe Item fonctionne de la manière suivante : dans votre base imaginons que nous ayons 10 Items, chacun de ces Items référencent un Acheteur avec une relation un-à-un. La relation est mappée avec un proxy en mode lazy=&nbsp;&raquo;true&nbsp;&raquo;. Si par défaut vous chargez la liste des Items puis qu&#8217;ensuite vous appelez la méthode getBuyer() pour retrouver l&#8217;acheteur, Hibernate effectuera alors 10 requêtes SQL (10 items).<br />
Le chargement par lot permet de spécifier à Hibernate la taille d&#8217;une fenêtre de chargement, en utilisant les clés primaires ou les clés étrangères, qui ici seraient dans la table BUYER.</p>
<p><code><br />
&lt;class name="Item" batch-size="10"&gt;...&lt;/class&gt;<br />
</code><br />
Hibernate exécutera non plus 10 requêtes SQL mais une seule pour charger l&#8217;ensemble des acheteurs. Idéalement vous l&#8217;aurez deviné, ce batch-size correspond au nombre d&#8217;éléments affichés sur une page Web, dans un tableau paginé par exemple.</p>
<p>Ensuite il est possible d&#8217;activer le chargement par lot sur les collections. Notre Item a une collection de Bid. Imaginons que je charge mes 10 Items dans ma Session Hibernate. Par défaut, chaque appel à getBids() entrainera alors une requête SQL, soit 10 requêtes pour charger chacune des collections. En spécifiant une taille de batch comme ci-dessous il est possible d&#8217;optimiser le nombre d&#8217;éléments préchargés et donc de réduire le nombre de requête SQL :</p>
<pre name="code" class="xml">
&lt;class name="Item"&gt;
    &lt;set name="Bid" batch-size="3"&gt;
        ...
    &lt;/set&gt;
&lt;/class&gt;
</pre>
<p>Pour 7 enchères, Hibernate chargera alors 3,3,1 en effectuant 3 requêtes au lieu de 7 requêtes par exemple. Cela permet de réduire encore une fois le nombre de requête, et cette valeur doit être guidée par le code de votre application et l&#8217;usage que vous en faîtes.<br />
Pour cette raison il est souvent plus souhaitable de définir la taille du batch au niveau du code comme dans ci-dessous:</p>
<pre>
 List l = session.createCriteria(Item.class)
                    .setFetchSize(10)
                    .list();
</pre>
<p>Petite astuce au passage : si vous souhaitez limiter le nombre de résultat retourné, vous connaissez sans doute la commande setMaxResults(int i).</p>
<pre>
List l = session.createCriteria(Item.class)
                    .setMaxResults(10)
                    .list();
</pre>
<p>Par curiosité je me suis un peu demandé pourquoi Hibernate prend plus de temps que la même requête SQL exécutée sur ma base de test. En fouillant un peu je me suis aperçu qu&#8217;il est intéressant de préciser la taille du batch afin de ne récuperer qu&#8217;un seul lot :</p>
<pre>
List l = session.createCriteria(Item.class)
                    .setMaxResults(10)
                    .setFetchSize(10)
                    .list();
</pre>
<p>Essayez sur votre code vous verrez la différence.</p>
<h3>Conclusion de la première partie</h3>
<p>Lorsque le chargement tardif ne peut pas être utilisé, il est important de vérifier que la stratégie par défaut d&#8217;Hibernate n&#8217;entraine pas un nombre de requêtes SQL trop importantes. Il est possible de limiter ce nombre en utilisant des jointures ou des selections imbriquées. A vous ensuite d&#8217;optimiser vos réglages selon votre application.</p>
<p>Dans la deuxième partie nous allons voir les 3 paramètres différents de chargement des collections.</p>
<p>J&#8217;espère que vous aurez apprécié. Si vous voulez aller un peu plus loin, il ne vous reste plus qu&#8217;à télécharger le code de l&#8217;article et à commencer à tester les différentes configurations possibles d&#8217;Hibernate. Pour les personnes à la recherche d&#8217;un tutorial sur Hibernate, la première partie vous montre un exemple simple de mapping pour commencer à apprendre Hibernate.</p>
<p>Bonne lecture et à bientôt pour la deuxième partie !</p>
<p><strong>Code source de l&#8217;article</strong><br />
<a href="http://www.touilleur-express.fr/divers/ArticleTouilleur_hibernate.tar.gz">ArticleTouilleur_hibernate.tar.gz</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.touilleur-express.fr/2009/03/03/hibernate-gerer-le-chargement-des-associations-efficacement/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

