| Gestion des connexions non-transactionnelles #4 |
Jeudi, 31 Mars 2011 07:01
|
|
Dans l'article précédent, nous avons mis en place la solution nous permettant de libérer des connexions inutiles le temps qu'une partie du process, sans relation avec la base de données, puisse s'exécuter. Nous avons cependant constaté que deux appels successifs, sur les mêmes données, ouvraient les mêmes connexions. Dans ce dernier article de la série, nous allons mettre en place un cache et constater son effet sur les ouvertures et fermetures de connexions. Configuration d'HibernateLa mise en place du cache de niveau 2 se fait de manière on-ne-peut-plus classique, en le basant sur ehCache. Voici l'ajout sur le fichier hibernate.cfg.xml : <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</property> <property name="net.sf.ehcache.configurationResourceName">ehcache/ehcacheRoWithCache.xml</property> <property name="hibernate.cache.use_query_cache">true</property> Configuration d'ehCacheVoici la configuration de ehCache, volontairement très basique étant donné la complexité du projet exemple : <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <diskstore path="java.io.tmpdir"></diskstore> <defaultcache eternal="false" maxelementsinmemory="10000" overflowtodisk="false" timetoidleseconds="60" timetoliveseconds="60"></defaultcache> </ehcache> Configuration de SpringLa configuration de Spring se base presque totalement sur celle de la configuration précédente (cf. import du fichier applicationContext-read-only.xml), à laquelle on ajoute simplement un accès facilité au cache pour les tests : <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <import resource="applicationContext-read-only.xml"></import> <bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" id="cacheManager"> <property name="configLocation" value="classpath:ehcache/ehcache.xml"></property> <property name="shared" value="true"></property> </bean> <bean class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" id="sessionFactory" scope="singleton"> <property name="configLocation" value="classpath:hibernate/hibernate-read-only-with-cache.cfg.xml"></property> <property name="dataSource" ref="datasource"></property> </bean> </beans> Résultat sur la méthode getCustomerSi nous étudions maintenant le résultat de la configuration sur la méthode getCustomer, pour le premier appel, nous constatons qu'il se déroule exactement de la même façon que lors de la configuration précédente : 09:35:47.797 [INFO ] com.thug.AbstractTestCase - First call to facade. 09:35:47.823 [INFO ] com.thug.facade.CustomerFacade - start - getCustomer 09:35:47.823 [INFO ] com.thug.dao.CustomerDao - get customer 09:35:47.828 [INFO ] com.thug.tools.MyBasicDataSource - Opening connection Hibernate: select customer0_.id_customer as id1_0_0_, customer0_.first_name as first2_0_0_, customer0_.last_name as last3_0_0_ from customer customer0_ where customer0_.id_customer=? 09:35:47.844 [INFO ] com.thug.tools.MyConnection - Closing connection. 09:35:47.844 [INFO ] com.thug.facade.CustomerFacade - Calling Web Services... 09:35:47.844 [INFO ] com.thug.client.ProviderWs - Web Services are slow. 09:35:52.873 [INFO ] com.thug.facade.CustomerFacade - Web Services called. 09:35:52.873 [INFO ] com.thug.facade.CustomerFacade - Getting lazy addresses... 09:35:52.876 [INFO ] com.thug.tools.MyBasicDataSource - Opening connection Hibernate: select addresses0_.id_customer as id4_1_, addresses0_.id_address as id1_1_, addresses0_.id_address as id1_1_0_, addresses0_.city as city1_0_, addresses0_.country as country1_0_ from address addresses0_ where addresses0_.id_customer=? 09:35:52.886 [INFO ] com.thug.tools.MyConnection - Closing connection. 09:35:52.890 [INFO ] com.thug.facade.CustomerFacade - Lazy addresses got. 09:35:52.891 [INFO ] com.thug.facade.CustomerFacade - end (5068ms) Jusque là, tout est logique : un cache n'est jamais utile lors du premier appel. En revanche, si l'on étudie les logs du deuxième appel : 09:35:52.920 [INFO ] com.thug.AbstractTestCase - Second call to facade. 09:35:52.920 [INFO ] com.thug.facade.CustomerFacade - start - getCustomer 09:35:52.920 [INFO ] com.thug.dao.CustomerDao - get customer 09:35:52.921 [INFO ] com.thug.facade.CustomerFacade - Calling Web Services... 09:35:52.921 [INFO ] com.thug.client.ProviderWs - Web Services are slow. 09:35:57.922 [INFO ] com.thug.facade.CustomerFacade - Web Services called. 09:35:57.922 [INFO ] com.thug.facade.CustomerFacade - Getting lazy addresses... 09:35:57.924 [INFO ] com.thug.facade.CustomerFacade - Lazy addresses got. 09:35:57.924 [INFO ] com.thug.facade.CustomerFacade - end (5004ms) Magique ! Aucune connexion n'a été ouverte. Etant donné que seule la session Hibernate était ouverte au moment de récupérer les informations et que ces dernières étaient contenues dans le cache, Hibernate n'a pas eu besoin d'exécuter la moindre requête SQL et n'a donc pas demandé l'ouverture d'une connexion sur la base de données. Il est particulièrement intéressant de constater cette absence d'ouverture/fermeture : ce deuxième appel, bénéficiant du cache, n'a été en aucun cas consommateur pour le pool de connexion ; et ne gène ni n'est gêné par un possible engorgement des appels utilisant la base de données. Ce comportement, pour en apprécier tout le bénéfice, est à comparer avec une configuration classique qui, même si elle bénéficie du cache, ouvre systématiquement une connexion. Cette dernière est alors doublement inutile : non seulement elle est monopolisée pendant tout le temps de l'appel, mais en plus, elle n'est jamais réellement utilisée. ConclusionAu cours de cette série d'articles, nous avons montré l'inconvénient de la configuration standard que l'on trouve habituellement dans tous les projets, proposé une solution alternative résolvant le problème initial, solution que nous avons critiquée et pour laquelle nous avons exposé les points faibles (cf. article précédent). Nous avons enfin amélioré la solution pour libérer de toute connexion les appels correctement configurés. Il est cependant important de préciser que la configuration que nous vous proposons fonctionne dans un cas standard relativement simple (multicouches façade/servide/dao + lazy-loading). Si votre application se base sur des patterns asynchrones ou multithreadés, il est important de mettre en place des tests de non-régression solides vérifiant le bon fonctionnement de votre configuration. Pour aller plus loin, vous pouvez très bien repenser une partie de votre application de façon à mixer des solutions plus classiques pour vos cas d'utilisations les plus complexes avec une solution du type que nous avons proposé pour les cas plus simples ou standards. Vous pouvez également y associer d'autres techniques, comme l'excellent LazyConnectionDataSourceProxy qui vous permet de retarder l'ouverture de la connexion le plus tard possible voire de ne pas l'ouvrir, et ce avec un minimum de configuration. |
| Mise à jour le Jeudi, 31 Mars 2011 07:21 |



