[Hibernate] gestion du cache, best practices et sessions

gestion du cache, best practices et sessions [Hibernate] - Java - Programmation

Marsh Posté le 26-06-2008 à 11:06:17    

Bonjour tout le monde,
 
Je suis actuellement entrain de réaliser la partie traitement de données d'une application web. Pour cela, j'ai décidé d'utiliser le duo Hibernate et PostGres. J'ai réaliser tout le traitement de donnée en utilisant les méthodes save, update et les criterions qui semblaient adaptés dans le cadre de requêtes simples. J'ai testé les performances de mes traitements à l'aide de tests unitaires en localhost (plus de 1000 fois chaque requête pour connaitre le temps de réponse moyen, sans vider le cache). Certaines requêtes mettent presque 1 seconde à l'exécution. J'ai chercher sur le net, j'ai remarqué que l'utilisation des deux caches de Hibernates (requête et secondaire) et la bonne gestion des sessions pouvaient aider à diminuer le temps d'exécution des requêtes récurrentes. Le seul problème, c'est que je n'arrive pas à trouver de tutorial clair expliquant l'utilisation de tout ça.
 
Dés lors, j'aimerais des conseils sur les domaines suivants:
- la gestion des session afin d'avoir une gestion des données multi-threader optimale
- la gestion du cache de premier niveau de hibernate
- la gestion du cache de second niveau de hibernate
- les best practices sur Hibernate
 
Voici les différentes sources qui vous permettrons de comprendre un peu comment tout cela marche
 
Le fichier de config de hibernate :

Code :
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!DOCTYPE hibernate-configuration
  3.     PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
  4.     "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
  5. <hibernate-configuration>
  6. <session-factory >
  7.  <!-- local connection properties -->
  8.  <property name="hibernate.connection.url">jdbc:postgresql://localhost/Report</property>
  9.  <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
  10.  <property name="hibernate.connection.username">mathieu</property>
  11.  <property name="hibernate.connection.password">mathieu</property>
  12.  <!-- property name="hibernate.connection.pool_size"></property -->
  13.  <!-- EhCache -->
  14.  <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
  15.  <property name="hibernate.cache.use_query_cache">true</property>
  16.  <property name="hibernate.cache.use_second_level_cache">true</property>
  17.  <property name="hibernate.cache.use_structured_cache">true</property>
  18.  <!-- dialect for PostgreSQL -->
  19.         <property name="dialect">org.hibernate.dialect.PostgreSQLDialect</property>
  20.         <property name="hibernate.show_sql">false</property>
  21.         <property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
  22.  <!-- mapping for Table -->
  23.  <mapping resource="com/modele/donnees/Message.hbm.xml"/>
  24. </session-factory>
  25. </hibernate-configuration>


le fichier de mapping de Message :

Code :
  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC
  3. "-//Hibernate/Hibernate Mapping DTD//EN"
  4. "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
  5. <hibernate-mapping package="com.modele.donnees">
  6. <class
  7.  name="Message"
  8.  table="message"
  9. >
  10.  <meta attribute="sync-DAO">false</meta>
  11.  <id
  12.   name="Id"
  13.   type="integer"
  14.   column="message_id"
  15.  >
  16.   <generator class="sequence"/>
  17.  </id>
  18.  <property
  19.   name="TypeMessage"
  20.   column="type_message"
  21.   type="string"
  22.   not-null="false"
  23.   length="15"
  24.  />
  25.  <property
  26.   name="Dateecriture"
  27.   column="dateecriture"
  28.   type="date"
  29.   not-null="false"
  30.   length="13"
  31.  />
  32.  <property
  33.   name="Masquer"
  34.   column="masquer"
  35.   type="boolean"
  36.   not-null="false"
  37.   length="1"
  38.  />
  39.  <property
  40.   name="ContenuMessage"
  41.   column="contenu_message"
  42.   type="string"
  43.   not-null="false"
  44.  />
  45.  <many-to-one
  46.   name="Membre"
  47.   column="membre_id"
  48.   class="Membre"
  49.   not-null="true"
  50.  >
  51.  </many-to-one>
  52.  <set name="LienMembreCompteRendus" inverse="true">
  53.   <key column="message_id"/>
  54.   <one-to-many class="LienMembreCompteRendu"/>
  55.  </set>
  56. </class>
  57. </hibernate-mapping>


la classe de gestion des sessions : (j'avouais que je l'ai rouver sur le net et que je l'ai pas encore vraiment retravailler)

Code :
  1. package com.dao.connexion;
  2. import org.hibernate.HibernateException;
  3. import org.hibernate.SessionFactory;
  4. import org.hibernate.cfg.Configuration;
  5. import org.hibernate.classic.Session;
  6. public class connexion
  7. {
  8. private static final SessionFactory sessionFactory;
  9. static
  10. {
  11.  try
  12.  {
  13.   sessionFactory = new Configuration().configure().buildSessionFactory();
  14.  }
  15.  catch (HibernateException ex)
  16.  {
  17.   throw new RuntimeException("Exception building SessionFactory: "+ ex.getMessage(), ex);
  18.  }
  19. }
  20. public static final ThreadLocal session = new ThreadLocal();
  21. public static Session currentSession() throws HibernateException
  22. {
  23.  Session s = (Session) session.get();
  24.  // Open a new Session, if this Thread has none yet  
  25.  if (s == null)
  26.  {
  27.   s = sessionFactory.openSession();
  28.   session.set(s);
  29.  }
  30.  return s;
  31. }
  32. public static void closeSession() throws HibernateException
  33. {
  34.  Session s = (Session) session.get();
  35.  session.set(null);
  36.  if (s != null)
  37.   s.close();
  38. }
  39. }


le code permettant de creer un message :

Code :
  1. Session session = connexion.currentSession();
  2. Transaction transaction = session.beginTransaction();
  3. session.save(nouveauMessage);
  4. for (Iterator<LienMembreCompteRendu> iterator = lien.iterator(); iterator.hasNext();)
  5. session.save((LienMembreCompteRendu) iterator.next());
  6. transaction.commit();
  7. connexion.closeSession();


(Je crois que c'est pas terrible ce que je fais la, le close connexion  :sweat: )
 
et le fichier de recherche de message :

Code :
  1. Session session = connexion.currentSession();
  2. if (identifiant != "" )
  3. {
  4. try
  5. {
  6.  listMessage.add((Message)session.createCriteria(Message.class)
  7.        .add( Restrictions.eq("Id",Integer.parseInt(identifiant))).uniqueResult());
  8. }
  9. catch(ClassCastException ex){new RapportErreur(ex,classe);}
  10. }
  11. else
  12. listMessage = session.createCriteria(Message.class)
  13.     .list();


 
Voila. Pouvez vous me donner vos commentaires et me donner des petits conseils? Merci


---------------
En informatique, il n'y a pa de solution, que des problèmes :)
Reply

Marsh Posté le 26-06-2008 à 11:06:17   

Reply

Marsh Posté le 26-06-2008 à 14:30:53    

Je sais que c'est un peu long à lire, alors un peu de courage.  ;)
 
Si vous avez des liens ou autres vous gênez pas

Reply

Marsh Posté le 27-06-2008 à 15:34:01    

je connait plus la partie JPA de hibernate mais quand même une remarque:
On peux définir des "fetch plan" pour la lecture de donnée et des "cascade" pour le persist:
Du coup pas besoin de sauver LienMembreCompteRendu, le save de "message" suffit. (ca c'est pour le cascade, le fetch c'est pour la lecture et ne pas avoir des lazyException
 
Pour ce qui est de la fermeture de la session à chaque méthode c'est pas le mieux non plus il me semble.  
Voir le design pattern "open session in view" pour une solution simple (mais qui à des effets de bords)
 
Les annotations springs peuvent faire beaucoup. Pour une nouvelle application, je conseillerai de commencer avec JPA (et l'implémetation d'hibernate) plutot que du pur hibernate.

Reply

Marsh Posté le 27-06-2008 à 16:12:17    

tempo14 a écrit :

je connait plus la partie JPA de hibernate mais quand même une remarque:
On peux définir des "fetch plan" pour la lecture de donnée et des "cascade" pour le persist:
Du coup pas besoin de sauver LienMembreCompteRendu, le save de "message" suffit. (ca c'est pour le cascade, le fetch c'est pour la lecture et ne pas avoir des lazyException
 
Pour ce qui est de la fermeture de la session à chaque méthode c'est pas le mieux non plus il me semble.  
Voir le design pattern "open session in view" pour une solution simple (mais qui à des effets de bords)
 
Les annotations springs peuvent faire beaucoup. Pour une nouvelle application, je conseillerai de commencer avec JPA (et l'implémetation d'hibernate) plutot que du pur hibernate.


 
Merci pour les informations, j'étais justement entrain de regarder les fetch plan et plus particulièrement le lazy loading afin d'optimiser le chargement car j'ai l'impression qu'hybernate à une stratégie agressive de base. Je me suis également penché sur le cache, et à ce niveau la j'ai un problème curieux. Il met 2 fois plus de temps avec le cache que sans (cache de second niveau), je pense que j'ai pas bien paramétré tout ça encore. Je vais regardé le design pattern que tu me conseil aussi.
 
PS: pour ceux qui cherchent des informations sur le cache de second niveau d'hibernate allez voir ce site
http://www.devx.com/dbzone/Article/29685/0/page/1
Il est pas mal, en plus y a un exemple.

Reply

Marsh Posté le 27-06-2008 à 16:15:41    

par défaut (au moins en JPA) hibernate fait du lazy loading pour les type complexe, pas pour les String, Double et autre Integer

Message cité 1 fois
Message édité par tempo14 le 27-06-2008 à 16:17:12
Reply

Marsh Posté le 27-06-2008 à 16:19:01    

tempo14 a écrit :

par défaut (au moins en JPA) hibernate fait du lazy loading


Okay, je comprend pas d'où viens c'est temps de recherche excessif alors. Presque 1 seconde pour charger certaines données.

Reply

Marsh Posté le 27-06-2008 à 16:25:07    

par contre dans le lien que tu donnes, on voit que le cache de premier niveau est associé à la session. C'est pas une bonne idée de l'ouvrir et la fermer dans chaque méthode. Pour ce qui est du problème de performance, un "showsql=true" donne généralement de bonne indications

Reply

Marsh Posté le 27-06-2008 à 16:45:46    

tempo14 a écrit :

par contre dans le lien que tu donnes, on voit que le cache de premier niveau est associé à la session. C'est pas une bonne idée de l'ouvrir et la fermer dans chaque méthode. Pour ce qui est du problème de performance, un "showsql=true" donne généralement de bonne indications

 

Le cache de premier niveau n'est pas propre a une session? J'avais cru comprendre que le cache de premier niveau (requête) l'était et que le second niveau était commun à tous?

 

Au fait, je suis entrain de regarder la cascade sur le lien suivant (chapitre 21.3)
http://www.hibernate.org/hib_docs/ [...] child.html
Je comprend pas j'ai pas commit dans la base on diré? pourtant tout correspond aux explications et j'ai même testé avec une transaction?


Message édité par iviath le 27-06-2008 à 16:49:59
Reply

Marsh Posté le 27-06-2008 à 17:11:36    

Citation :

Le cache de premier niveau n'est pas propre a une session?


oui c'est ca, pourquoi? je ne dis pas l'inverse?
 

Citation :

Je comprend pas j'ai pas commit dans la base on diré? pourtant tout correspond aux explications et j'ai même testé avec une transaction?


Je pense que le commit au niveau de la db ne se fait pas nécessairement au "transaction.commit();" mais plutôt au "session.flush();" (à valider)

Reply

Marsh Posté le 27-06-2008 à 17:25:20    

tempo14 a écrit :

Citation :

Le cache de premier niveau n'est pas propre a une session?


oui c'est ca, pourquoi? je ne dis pas l'inverse?


 
Ben tu avais l'air de dire que le cache de premier niveau n'est pas propre à une session et j'ai cru comprendre que cache session avait son propre cache de requête.
 

tempo14 a écrit :


Citation :

Je comprend pas j'ai pas commit dans la base on diré? pourtant tout correspond aux explications et j'ai même testé avec une transaction?


Je pense que le commit au niveau de la db ne se fait pas nécessairement au "transaction.commit();" mais plutôt au "session.flush();" (à valider)


 

Code :
  1. Session session = connexion.currentSession();
  2. Base base = (Base)session.load(Base.class, id));
  3. base.addToDepartements(nouveauDepartement);
  4. session.flush();
  5. connexion.closeSession();


 
Déjà au debuger il me retourne pas une classe de type Base on dirait, et même quand la base est bonne il sauvegarde pas.

Reply

Marsh Posté le 27-06-2008 à 17:25:20   

Reply

Marsh Posté le 01-07-2008 à 15:20:12    

Le cache de requête

 

Pour tous ceux que ça intéresse, je viens de comprendre comment marcher le cache de requête. J'en fait tout de suite profiter tout le monde. Déjà, il faut savoir qu'il existe plusieurs fournisseurs de caches différents (tableau  19.1 page http://www.hibernate.org/hib_docs/ [...] ance-cache)  , personnellement j'ai utilisé ehcache.
Le cache de requete se paramètre à plusieurs niveaux:
- dans le fichier de config de hibernate (ici le hibernate.cfg.xml)
- dans un fichier ehcache.xml dans le classpath

 

dans le fichier de config d'hibernate il faut activer le fournisseur de cache et définir les classes l'implémentant:

Code :
  1. <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property> // permet de sélectionner le type de fournisseur
  2. <property name="hibernate.cache.use_query_cache">true</property> //permet d activer le cache de requete
  3. <property name="hibernate.cache.use_second_level_cache">false</property> //permet de désactiver le cache de second niveau (optionnel)
  4. <property name="hibernate.cache.use_structured_cache">true</property>
  5. <property name="hibernate.cache.use_minimal_puts">true</property> // permet d'optimiser l'utilisation du cache en clusters
 

ensuite regler le fichier ehcache.xml

 
Code :
  1. <ehcache>
  2. <defaultCache  // cache par defaut
  3.  maxElementsInMemory="10000" // nombre d'objet en cache
  4.  eternal="false" // persistance aprés arret
  5.  overflowToDisk="false" // ecriture sur le disque en cas de depassement mémoire
  6.  timeToIdleSeconds="120" // temps de persistance des objets
  7.  timeToLiveSeconds="120" // temps de vie des objets
  8.  diskPersistent="true" // ecriture sur le disque
  9.  diskExpiryThreadIntervalSeconds="120" // temps d expiration des threads
  10. />
  11. <cache
  12.  name="net.sf.hibernate.cache.StandardQueryCache" // cache de requete
  13.  maxElementsInMemory="10"
  14.  eternal="false"
  15.  timeToLiveSeconds="120"
  16.  overflowToDisk="false"
  17.  />
  18. </ehcache>


Message édité par iviath le 02-07-2008 à 11:36:57
Reply

Marsh Posté le 02-07-2008 à 11:43:04    

best practices

 

Voici l'ensmble des best practices que j'ai trouver sur le net, si vous avez des suggestion, ne vous gênez pas.

 

- la recherche d'un objet dans une table se fait toujours à l'aide d'un load ou get. Ceci permet de rechercher dan un premier temps dans le cache avant de rechercher dans la table. Il existe une différence fondamentale entre le load et le get. En cas d'objet non trouver dans la table le get retourne un objet null et le load soulève une exception.
exemple:

Code :
  1. Object objet = session.load(MaTable.class,MonIdentifiant);


- Les criteria / criterions sont à utiliser de préférence pour les requêtes simples et le HQL dans le cadre de requête plus complexe.


Message édité par iviath le 02-07-2008 à 11:43:20
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed