home : Développement : Base de données :
Gestion des connexions non-transactionnelles #2
Mardi, 15 Mars 2011 16:24 PDF Imprimer Envoyer

Dans un premier article, nous avons posé les bases du problème qui nous occupe et présenté le projet qui va nous servir de base pour effectuer nos tests.

Dans ce deuxième article, nous allons présenter la solution classique qui consiste à brancher une ouverture et fermeture de connexion directement sur la couche Façade.

Présentation théorique du branchement de la solution

Si l'on reprend le diagramme de séquence proposé dans le premier article, voilà à quoi ressemblera le branchement de notre transaction sur notre méthode getCustomer :

Branchement de la connexion sur la façade

  1. On ouvre la transaction et sa connexion au moment de l'appel à la façade,
  2. On ferme la transaction et la connexion à la fin de l'appel,
  3. L'appel au Web Service peut prendre du temps et monopoliser une connexion inutile.

En vert, la période pendant laquelle la connexion est ouverte. En rouge, la période pendant laquelle elle est vraiment utile. Plus l'espacement entre les deux périodes rouges est important (dû au temps de réponse du Web Service), plus garder une connexion ouverte est inutile.

Mapping Hibernate

Hibernate est configuré de manière parfaitement classique. Nous vous invitons à vous rapprocher du projet exemple pour en prendre connaissance. Nous ferons simplement noter que la relation entre un client et ses adresses est en lazy-loading. Cela permet de simuler un accès à la base de données après le retour du Web Service fournisseur, en ne passant pas par un DAO.

Configuration de Spring

 
<bean class="org.springframework.orm.hibernate3.LocalSessionFactoryBean" id="sessionFactory" scope="singleton">
  <property name="configLocation" value="classpath:hibernate/hibernate-all-transactional.cfg.xml"></property>
  <property name="dataSource" ref="datasource"></property>
</bean>
 
 
<bean class="org.springframework.orm.hibernate3.HibernateTransactionManager" id="transactionManager">
  <property name="sessionFactory" ref="sessionFactory"></property>
</bean>
 
 
<bean class="org.springframework.transaction.interceptor.TransactionInterceptor" id="transactionInterceptor">
  <property name="transactionManager" ref="transactionManager"></property>
  <property name="transactionAttributeSource">
    <value>
      com.thug.facade.CustomerFacade.*=PROPAGATION_REQUIRED,-Throwable
    </value>
  </property>
</bean>
 
 
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" id="transactionBeanNameProxyCreator">
  <property name="proxyTargetClass" value="true"></property>
  <property name="beanNames">
    <list>
      <value>customerFacade</value>
    </list>
  </property>
  <property name="interceptorNames">
    <list>
      <value>transactionInterceptor</value>
    </list>
  </property>
</bean>

Rien de très spécifique ici : un transaction manager, on-ne-peut-plus classique, est branché sur toutes les méthodes de la couche Façade.

Résultat sur la méthode getCustomer

Étudions un peu en détails les logs de l'appel à la méthode getCustomer. On constate dès le début qu'une connexion est ouverte juste avant l'appel à la méthode de la façade : c'est l'intercepteur qui a correctement fait son travail et se charge d'ouvrir la transaction et la session Hibernate dès le début.

 
17:06:11.374 [INFO ] com.thug.AbstractTestCase - First call to facade.
17:06:11.417 [INFO ] com.thug.tools.MyBasicDataSource - Opening connection
17:06:11.419 [INFO ] com.thug.facade.CustomerFacade - start - getCustomer
17:06:11.419 [INFO ] com.thug.dao.CustomerDao - get customer

Nous sommes ici dans la couche Dao. Une première requête SQL est demandée par Hibernate pour remonter le client dont on a reçu l'identifiant.

 
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=?
17:06:11.435 [INFO ] com.thug.facade.CustomerFacade - Calling Web Services...
17:06:11.435 [INFO ] com.thug.client.ProviderWs - Web Services are slow.
17:06:16.538 [INFO ] com.thug.facade.CustomerFacade - Web Services called.

L'appel au Web Service vient de se terminer après 5 secondes pendant laquelle la connexion a été monopolisée. Nous nous préparons à retourner le client à notre utilisateur, et nous faisons pour cela appel aux données restées en base, puisqu'elles étaient lazy-loadées, avec une deuxième requête SQL. Cet appel n'a pas de valeur métier dans notre exemple mais pourrait très bien survenir lors d'une opération de conversion sur notre objet de façade.

 
17:06:16.538 [INFO ] com.thug.facade.CustomerFacade - Getting lazy addresses...
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=?
17:06:16.542 [INFO ] com.thug.facade.CustomerFacade - Lazy addresses got.
17:06:16.542 [INFO ] com.thug.facade.CustomerFacade - end (5123ms)
17:06:16.551 [INFO ] com.thug.tools.MyConnection - Closing connection.

Le premier appel à la façade est terminé : la connexion se ferme automatiquement grâce à l'intercepteur.

Nous faisons un deuxième appel identique, depuis notre test, à la façade. Dans le cas présent, ce second appel ne sert à rien hormis à montrer qu'il se passe exactement comme le premier : ouverture d'une connexion, 2 requêtes SQL puis fermeture de connexion. Ce deuxième appel servira d'étalon pour les solutions futures.

 
17:06:16.563 [INFO ] com.thug.AbstractTestCase - Second call to facade.
17:06:16.564 [INFO ] com.thug.tools.MyBasicDataSource - Opening connection
17:06:16.564 [INFO ] com.thug.facade.CustomerFacade - start - getCustomer
17:06:16.564 [INFO ] com.thug.dao.CustomerDao - get customer
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=?
17:06:16.564 [INFO ] com.thug.facade.CustomerFacade - Calling Web Services...
17:06:16.564 [INFO ] com.thug.client.ProviderWs - Web Services are slow.
17:06:21.565 [INFO ] com.thug.facade.CustomerFacade - Web Services called.
17:06:21.566 [INFO ] com.thug.facade.CustomerFacade - Getting lazy addresses...
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=?
17:06:21.568 [INFO ] com.thug.facade.CustomerFacade - Lazy addresses got.
17:06:21.568 [INFO ] com.thug.facade.CustomerFacade - end (5004ms)
17:06:21.569 [INFO ] com.thug.tools.MyConnection - Closing connection.

Résultat sur la méthode updateCustomerName

Étudions de la même façon les logs de la méthode updateCustomerName :

 
17:06:21.604 [INFO ] com.thug.tools.MyBasicDataSource - Opening connection
17:06:21.604 [INFO ] com.thug.facade.CustomerFacade - start - updateCustomerName
17:06:21.604 [INFO ] com.thug.dao.CustomerDao - get customer
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=?
17:06:21.604 [INFO ] com.thug.facade.CustomerFacade - Calling Web Services...
17:06:21.604 [INFO ] com.thug.client.ProviderWs - Web Services are slow.
17:06:26.604 [INFO ] com.thug.facade.CustomerFacade - Web Services called.
17:06:26.604 [INFO ] com.thug.facade.CustomerFacade - Getting lazy addresses...
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=?
17:06:26.607 [INFO ] com.thug.facade.CustomerFacade - Lazy addresses got.
17:06:26.607 [INFO ] com.thug.facade.CustomerFacade - Setting new last name...
17:06:26.607 [INFO ] com.thug.facade.CustomerFacade - Last name set.
17:06:26.607 [INFO ] com.thug.facade.CustomerFacade - Updating customer...
17:06:26.607 [INFO ] com.thug.dao.CustomerDao - update customer
17:06:26.609 [INFO ] com.thug.facade.CustomerFacade - Customer updated.
17:06:26.609 [INFO ] com.thug.facade.CustomerFacade - end (5005ms)
Hibernate: update customer set first_name=?, last_name=? where id_customer=?
17:06:26.627 [INFO ] com.thug.tools.MyConnection - Closing connection.
 

L'ouverture et la fermeture de la connexion se déroulent exactement de la même façon : avant et après la façade. Notez que la requête de mise à jour n'est passée qu'au tout dernier moment. En effet, la connexion est transactionnelle et le commit global ne s'effectue qu'à la fin, une fois que l'on s'est assuré qu'aucune erreur ne soit arrivée.

Amélioration de la solution

Nous avons ici posé les bases et constaté ce qui se passait dans le cas classique. Dans le prochain article, nous allons commencer à améliorer la solution en permettant la fermeture de la connexion lorsqu'elle n'est plus nécessaire.

Il est également à noter qu'à partir de maintenant, ni le code métier ni le code des tests ne seront modifiés. Seules les configurations de Spring et d'Hibernate vont subir quelques évolutions.

Mise à jour le Jeudi, 31 Mars 2011 07:22 Thomas Huguerre
 

Ajouter un Commentaire


Code de sécurité
Rafraîchir