Voici la suite de mon article précédent sur Hibernate et le chargement des associations.
Il existe 3 paramètres possible pour l’élément lazy lors de la définition d’une association dans une entité :
– false
– true
– extra
......
Le test est simple : j’édite le fichier Item.hbm.xml et j’active le chargement tardif (lazy=true). Le code Java affiche le nom de l’item chargé puis ensuite le nombre d’enchères sur l’Item en utilisant la méthode size() sur le HashSet.
Code 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));
Résultats de l’exécution
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
On constate qu’Hibernate effectue tout d’abord une requête sur la table Item, affiche le nom de l’item puis ensuite effectue le chargement de l’ensemble des Bids en spécifiant l’id de l’Item. Je vois plusieurs soucis : le chargement de l’ensemble des colonnes de la table Bid, la deuxième requête, et finalement la requête SQL n’est pas franchement optimisée : nous voulons afficher la taille de la collection, or Hibernate charge l’ensemble des éléments…
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().
Je ne suis pas sûr d’être clair : pour retourner la taille de la collection, Hibernate charge toute la collection et retourne la taille de la liste… Vous imaginez la consommation en terme de mémoire ? Je serai presque tenté de dire qu’Hibernate n’est pas franchement écologique.
Voyons ce qu’il se passe lorsque le chargement tardif est désactivé en mettant lazy=false dans l’attribut set, dans le fichier Item.hbm.xml
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
Nous constatons que la seule différence, c’est qu’Hibernate effectue toutes les requêtes avant l’exécution du code…
Mais encore une fois, si notre code se contentait d’afficer par exemple le nombre d’enchères (ici 10) nous aurions chargé encore une fois l’arbre d’enchères pour rien. Bref pas d’optimisation en vue pour l’instant.
Voyons pour terminer ce qu’Hibernate fait lorsqe le paramètre lazy est mis à extra :
......
L’exécution nous donne le résultat suivant:
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
Cette fois-ci Hibernate utilise un SELECT COUNT pour calculer la taille de la collection sans la charger, ce qui est d’une part plus rapide, et d’autre part plus économique en terme de données chargées et d’instance préparées.
La valeur lazy est un moyen d’indiquer à Hibernate que l’objet Père (ici Item) contient une collection d’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.
C’est donc une optimisation possible dans une application afin d’éviter de charger un arbre d’objet trop important dans votre code.
Si vous utilisez les annotations voici comment déclarer ce paramère :
// Code de la classe Item @OneToMany @org.hibernate.annoations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.EXTRA) private Set bids = new HashSet();
Voilà pour la deuxième partie, j’ai encore quelques idées que je vous proposerai dans d’autres articles prochainement.