IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

86. La gestion des transactions avec Spring

 

chapitre 8 6

 

 

Niveau : niveau 4 Supérieur 

 

Spring permet une gestion et une propagation des transactions. Depuis Spring 2.0, les transactions sont mises en oeuvre en utilisant l'AOP.

Généralement, c'est la couche service qui assure la gestion des transactions des traitements. La déclaration des méthodes qui doivent être transactionnelles peut se faire par déclaration dans la configuration de Spring ou par des annotations.

L'utilisation des transactions peut se faire par déclaration ou par programmation en utilisant une API dédiée. L'utilisation des transactions de manière déclarative est la façon la plus simple de les mettre en oeuvre car c'est celle qui limite les impacts dans le code de l'application.

La déclaration du comportement transactionnel se fait au niveau des méthodes de toutes les classes concernées. Cependant, Spring n'est pas en mesure de propager un contexte transactionnel dans des appels de méthodes distantes.

Depuis Spring 2.0, il n'est plus nécessaire de déclarer un bean de type TransactionProxyFactoryBean mais il faut utiliser les tags de l'espace de nommage tx.

La mise en oeuvre des transactions avec Spring se fait essentiellement de manière déclarative : la façon la plus simple d'utiliser une transaction avec Spring est d'ajouter la déclaration de l'espace de nommage tx, le tag <tx:annotation-driven/> dans le fichier de configuration et d'utiliser le tag @Transaction sur les classes et/ou les méthodes concernées.

Ce chapitre contient plusieurs sections :

 

86.1. La gestion des transactions par Spring

La mise en oeuvre des transactions repose sur une abstraction qui permet de les mettre en oeuvre de façon similaire quelle que soit l'implémentation sous-jacente de la gestion des transactions (transactions globales avec JTA ou transactions locales avec JDBC, JPA, JDO, Hibernate, ...).

Un gestionnaire de transactions doit implémenter l'interface org.springframework.transaction.PlatformTransactionManager.

Spring propose plusieurs gestionnaires de transactions notamment :

  • org.springframework.orm.hibernate3.HibernateTransactionManager : pour utiliser Hibernate
  • org.springframework.transaction.jta.JtaTransactionManager : pour utiliser une implémentation de JTA fournie par un serveur d'applications
  • org.springframework.jdbc.datasource.DataSourceTransactionManager : pour utiliser une datasource avec JDBC

L'utilisation des transactions est ainsi identique quelque soit la solution utilisée : seule la déclaration d'un gestionnaire de transactions est à faire et c'est lui qui va se charger de gérer les transactions de manière spécifique à la solution utilisée.

Une transaction gérée par Spring possède plusieurs caractéristiques :

  • isolation : permet de préciser le niveau d'isolation de la transaction par rapport aux autres transactions.
  • propagation : permet de préciser comment les traitements s'intègrent dans un contexte transactionnel
  • timout : le temps maximum durant lequel la transaction peut s'exécuter. Au delà, la transaction est annulée (rollback).
  • read only : permet de préciser si les données sont lues uniquement ou si elles peuvent être mises à jour ceci afin de permettre certaines optimisations

 

86.2. La propagation des transactions

Une transaction peut être logique ou physique.

Une transaction logique est gérée par Spring : une ou plusieurs transactions logiques permettent à Spring de déterminer le statut de la transaction physique.

La propagation PROPAGATION_REQUIRED crée une transaction logique pour chaque méthode dont le contexte transactionnel possède ce type de propagation. Durant la portée de cette transaction logique, celle-ci peut être validée ou annulée.

Comme les transactions logiques peuvent être imbriquées, pour indiquer à une transaction englobante qu'une transaction sous-jacente a été annulée, une exception de type UnexpectedRollbackException est levée.

La propagation PROPAGATION_REQUIRES_NEW crée une nouvelle transaction indépendante pour chaque méthode dont le contexte transactionnel possède ce type de propagation. Chaque contexte transactionnel dispose de sa propre transaction physique. Le rollback de la transaction n'a aucune incidence sur le rollback d'une transaction englobante.

La propagation PROPAGATION_NESTED utilise une seule transaction physique avec des savepoints. Il est donc possible de faire un rollback dans le contexte transactionnel jusqu'au précédent savepoint sans annuler l'intégralité de la transaction physique sous-jacente qui poursuit son exécution. Le gestionnaire de transaction doit permettre un support des savepoints ce qui pour le moment n'est possible qu'avec le DataSourceTransactionManager qui utilise les transactions JDBC.

 

86.3. L'utilisation des transactions de manière déclarative

Historiquement, l'utilisation des transactions avec Spring se faisait de manière déclarative dans le fichier de configuration XML. Cette déclaration pouvait devenir fastidieuse et source d'erreur car relativement compliquée et lourde en fonction de la taille de l'application. La possibilité de déclarer les transactions avec des annotations a grandement simplifié la tâche du développeur.

Le support des transactions par Spring de manière déclarative se fait à deux niveaux :

  • une définition par des métadonnées en utilisant la configuration ou des annotations. Les métadonnées sont utilisées pour réaliser le tissage des aspects relatifs à la gestion des transactions (AspectJ).
  • l'utilisation de proxys grâce à Spring AOP

 

86.3.1. La déclaration des transactions dans la configuration du contexte

La déclaration des transactions dans la configuration du contexte se fait dans le fichier XML en utilisant les espaces de nommages tx et aop.

Il faut définir un gestionnaire de transactions (TransactionManager) spécifique au mode de fonctionnement des accès aux données.

Il faut définir un advice lié au gestionnaire de transactions qui va permettre de déclarer le mode de propagation des transactions dans les méthodes désignées par des motifs.

Il faut enfin définir la configuration des aspects à tisser sur les méthodes qui doivent être transactionnelles. Celles-ci sont définies par des points de coupe grâce à des motifs auxquels sont associés un advice.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
  </bean>

  <bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource" />
  </bean>

  <tx:advice id="serviceTxAdvice" transaction-manager="txManager">
    <tx:attributes>
      <tx:method name="find*" propagation="REQUIRED"
        read-only="true" />
      <tx:method name="*" propagation="REQUIRED" />
    </tx:attributes>

  </tx:advice>

  <tx:advice id="daoTxAdvice" transaction-manager="txManager">
    <tx:attributes>
      <tx:method name="find*" propagation="REQUIRED" />
      <tx:method name="*" propagation="MANDATORY" />
    </tx:attributes>
  </tx:advice>

  <aop:config>
    <aop:pointcut id="serviceMethodes"
      expression="execution(*com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
    <aop:advisor advice-ref="serviceTxAdvice" pointcut-ref="serviceMethodes" />
  </aop:config>

  <aop:config>
    <aop:pointcut id="daoMethodes"
      expression="execution(*com.jmdoudoux.test.spring.dao.*DaoImpl*.*(..))" />
    <aop:advisor advice-ref="daoTxAdvice" pointcut-ref="daoMethodes" />
  </aop:config>

</beans>

Un advice est défini sous le nom txAdvice en lui associant le TransactionManager grâce à l'attribut transaction-manager : les méthodes dont le nom commence par get sont en lecture seule, les autres méthodes sont en lecture/écriture qui est le mode par défaut.

Le tag <tx:advice/> possède plusieurs attributs :

nom de l'attribut

Rôle

Valeur par défaut

Propagation

Préciser le mode de propagation de la transaction

REQUIRED

Isolation

Préciser le niveau d'isolation

DEFAULT

Transaction

Préciser si la transaction est en lecture seule ou lecture/écriture

read/write

Timeout

Préciser le timeout avant le rollback de la transaction

par défaut, c'est le timeout du gestionnaire de transactions sous-jacent utilisé, ou aucun si aucun timeout n'est supporté


Le tag <tx:method/> possède plusieurs attributs :

Nom de l'attribut

Rôle

Valeur par défaut

Name

nom de la ou des méthodes concernées en utilisant un motif dans lequel le caractère * peut être utilisé (exemple : get*) (obligatoire)

 

Propagation

mode de propagation de la transaction

REQUIRED

Isolation

niveau d'isolation de la transaction

DEFAULT

Timeout

timeout de la transaction en secondes

-1

read-only

la transaction est en mode lecture seule

No

rollback-for

la ou les exceptions (séparées par un caractère ";") qui provoquent un rollback de la transaction

 

no-rollback-for

la ou les exceptions (séparées par un caractère ";") qui ne provoquent pas un rollback de la transaction

 

Remarque : par défaut, toutes les exceptions de type RuntimeException provoquent un rollback mais les exceptions de type checked ne provoquent pas de rollback.

Le tag <aop:config> permet la configuration du tissage des aspects relatifs aux transactions en définissant un point de coupe précisé sous la forme d'une expression régulière d'AspectJ fournie comme valeur de l'attribut expression.

Grâce au tissage, l'advice sera exécuté lors de l'invocation de chaque méthode définie par le point de coupe.

Généralement, toutes les méthodes des services doivent être transactionnelles. Pour cela, le point de coupe doit utiliser une expression qui désigne toutes les méthodes et tous les services.

Exemple :
<aop:config>
  <aop:pointcut id="serviceMethodes" expression="execution(* 
com.jmdoudoux.test.spring.service.*.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethodes"/> 
</aop:config>

La gestion des transactions dans les services n'est pas toujours aussi générique et il peut être nécessaire de définir plusieurs pointcuts et advisors pour différentes configurations transactionnelles.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
  <aop:config>
    <aop:pointcut id="defaultServiceOperation"
      expression="execution(* com.jmdoudoux.test.spring.service.*Service.*(..))" />
    <aop:pointcut id="noTxServiceOperation"
      expression="execution(* com.jmdoudoux.test.spring.service.*Cache.*(..))" />

    <aop:advisor pointcut-ref="defaultServiceOperation"
      advice-ref="defaultTxAdvice" />
    <aop:advisor pointcut-ref="noTxServiceOperation"
      advice-ref="noTxAdvice" />
  </aop:config>

  <tx:advice id="defaultTxAdvice">
    <tx:attributes>
      <tx:method name="get*" read-only="true" />
      <tx:method name="*" />
    </tx:attributes>
  </tx:advice>

  <tx:advice id="noTxAdvice">
    <tx:attributes>
      <tx:method name="*" propagation="NEVER" />
    </tx:attributes>
  </tx:advice>

  <bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />

  <bean id="personneCache" class="com.jmdoudoux.test.spring.service.cache.PersonneCache" />

</beans> 

 

86.3.2. Un exemple de déclaration de transactions dans la configuration

Cette section fournit un exemple complet de déclaration d'un service dont les méthodes sont transactionnelles. La configuration des transactions se fait dans la configuration du contexte de l'application.

Le service est défini par une interface

Exemple :
package com.jmdoudoux.test.spring.service;

import java.util.List;

import com.jmdoudoux.test.spring.entite.Personne;

public interface PersonneService {
  void ajouter(Personne personne);

  Personne getParId(long id);

  List<Personne> getTous();

  void modifier(Personne personne);
}

Le service implémente l'interface.

Exemple :
package com.jmdoudoux.test.spring.service;

import java.util.List;
import com.jmdoudoux.test.spring.entite.Personne;

public class PersonneServiceImpl implements PersonneService {
  @Override
  public void ajouter(final Personne personne) {
    throw new UnsupportedOperationException();
  }
 
  @Override
  public Personne getParId(final long id) {
    throw new UnsupportedOperationException();
  }
 
  @Override
  public List<Personne> getTous() {
    throw new UnsupportedOperationException();
  }
 
  @Override
  public void modifier(final Personne personne) {
    throw new UnsupportedOperationException();
  }
}

Comme l'implémentation de toutes les méthodes du service lève une exception de type RuntimeException, les transactions provoqueront un rollback.

Les méthodes préfixées par get sont en lecture seule (read-only) alors que les autres méthodes sont utilisées pour des mises à jour (read-write).

Exemple :
<?xml version="1.0"
encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:aop="http://www.springframework.org/schema/aop"
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd

http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd

http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
      <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                  <tx:method name="get*" read-only="true" />
                  <tx:methodname="*" />
            </tx:attributes>
      </tx:advice>
      <aop:config>
            <aop:pointcut id="personneServiceOperation"
      expression="execution(* com.jmdoudoux.test.spring.service.PersonneService.*(..))" />
            <aop:advisor advice-ref="txAdvice" pointcut-ref="personneServiceOperation" />
      </aop:config>
      <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
            destroy-method="close">
            <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
            <property name="url" value="jdbc:derby://localhost/MaBaseDeTest" />
            <!--  property name="username" value="" / -->
            <!--   property name="password" value="" / -->
      </bean>
      <bean id="txManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="datasource" />
      </bean>
      <bean id="personneService" 
        class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans> 

Dans l'exemple ci-dessous, le point de coupe concerne toutes les méthodes de la classe PersonneService en définissant un advisor qui le lie à l'advice.

Le PlatformTransactionManager est défini sous la forme d'un bean nommé txManager.

L'utilisation des transactions est alors transparente dans le code appelant.

Exemple :
package com.jmdoudoux.test.spring;
import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.entite.Personne;
import com.jmdoudoux.test.spring.service.PersonneService;

public class MonApp {
  private static Logger LOGGER = Logger.getLogger(MonApp.class);
  
  public static void main(final String[] args) throws Exception {
    LOGGER.info("Debut de l'application");

    ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
        new String[] {"appContext.xml" });

    PersonneService personneService = (PersonneService) appContext
      .getBean("personneService");

    LOGGER.info("Debut invocation du service");
    try {
      personneService.ajouter(new Personne());
    } catch (Exception e) {
      LOGGER.error("exception " + e.getClass().getName() + " interceptee");
    }

    LOGGER.info("Fin invocation du service");
    LOGGER.info("Fin de l'application");
  }
}

L'application de test charge le contexte, lui demande une instance du service et invoque sa méthode ajouter().

Le niveau de traces dans le fichier de configuration de Log4J est configuré sur debug pour permettre de voir le détail des actions réalisées par Spring pour gérer les transactions.

Exemple :
<?xml version="1.0" encoding= "UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration
xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %p [%c] -%m%n"/>
    </layout>
  </appender>
 
  <root> 
    <priority value="debug"/>
    <appender-ref ref="stdout"/>
  </root>
</log4j:configuration>

Le classpath de l'application contient les bibliothèques requises :

org.springframework.transaction-3.0.5.RELEASE.jar, org.springframework.aop-3.0.5.RELEASE.jar, org.springframework.asm-3.0.5.RELEASE.jar, org.springframework.aspects-3.0.5.RELEASE.jar, org.springframework.beans-3.0.5.RELEASE.jar, org.springframework.context-3.0.5.RELEASE.jar, org.springframework.core-3.0.5.RELEASE.jar, org.springframework.expression-3.0.5.RELEASE.jar, com.springsource.org.apache.commons.logging-1.1.1.jar, com.springsource.org.aopalliance-1.0.0.jar, commons-dbcp-1.4.jar, com.springsource.org.apache.commons.pool-1.5.3.jar, org.springframework.jdbc-3.0.5.RELEASE.jar, derbyclient.jar, derbynet.jar, org.springframework.instrument-3.0.5.RELEASE.jar, spring-aspects-3.0.5.RELEASE.jar, log4j-1.2.16.jar

La JVM est lancée avec l'option -javaagent:C:/java/api/aspectjweaver/aspectjweaver-1.6.1.jar pour activer l'agent AspectJ qui va se charger de tisser les aspects au runtime, notamment ceux concernant les transactions déclarées dans la configuration.

Lors de l'exécution de l'application, l'appel de la méthode du service provoque un rollback puisqu'une exception de type runtime est levée durant ses traitements.

Résultat :
2011-04-28 22:16:07,937  INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
...
2011-04-28 22:16:10,000 DEBUG [org.springframework.beans.factory.support.
DefaultListableBeanFactory] Returning cached instance of singleton bean 'personneService'
2011-04-28 22:16:10,000  INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-04-28 22:16:10,031 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Creating new transaction with name 
[com.jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter]: 
PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2011-04-28 22:16:11,093 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Acquired Connection [jdbc:derby://localhost:1527/MaBaseDeTest, 
UserName=APP, Apache Derby Network Client JDBC Driver] for JDBC transaction
2011-04-28 22:16:11,109 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Switching JDBC Connection [jdbc:derby://localhost:1527/
MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver] to manual commit
2011-04-28 22:16:11,109 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Initiating transaction rollback
2011-04-28 22:16:11,109 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Rolling back JDBC transaction on Connection [jdbc:derby:
//localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver]
2011-04-28 22:16:11,109 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Releasing JDBC Connection [jdbc:derby://localhost:1527/
MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver] after transaction
2011-04-28 22:16:11,109 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils]
 Returning JDBC Connection to DataSource
2011-04-28 22:16:11,109 ERROR [com.jmdoudoux.test.spring.MonApp]
 exception java.lang.UnsupportedOperationException interceptee
2011-04-28 22:16:11,109  INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-04-28 22:16:11,109  INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application

Si la méthode du service ne lève pas d'exception durant son invocation, la transaction est validée par un commit.

Résultat :
2011-04-28 22:18:05,609  INFO
[com.jmdoudoux.test.spring.MonApp] Debut de l'application
...
2011-04-28 22:18:07,625  INFO
[com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-04-28 22:18:07,671 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Creating new
transaction with name [com.jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter]:
PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2011-04-28 22:18:08,703 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Acquired
Connection [jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache
Derby Network Client JDBC Driver] for JDBC transaction
2011-04-28 22:18:08,734 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Switching
JDBC Connection [jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache
Derby Network Client JDBC Driver] to manual commit
2011-04-28 22:18:08,734 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Initiating
transaction commit
2011-04-28 22:18:08,734 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Committing
JDBC transaction on Connection [jdbc:derby://localhost:1527/MaBaseDeTest,
UserName=APP, Apache Derby Network Client JDBC Driver]
2011-04-28 22:18:08,734 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Releasing
JDBC Connection [jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache
Derby Network Client JDBC Driver] after transaction
2011-04-28 22:18:08,734 DEBUG
[org.springframework.jdbc.datasource.DataSourceUtils] Returning JDBC Connection
to DataSource
2011-04-28 22:18:08,734  INFO
[com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-04-28 22:18:08,734  INFO
[com.jmdoudoux.test.spring.MonApp] Fin de l'application

 

86.4. La déclaration des transactions avec des annotations

L'annotation @Transactional peut être utilisée pour indiquer au conteneur les méthodes qui doivent s'exécuter dans un contexte transactionnel.

Si la déclaration des transactions se fait avec des annotations, il est tout de même nécessaire de déclarer le gestionnaire de transactions dans la configuration du contexte de Spring.

Pour permettre une utilisation de l'annotation @Transactional, il faut utiliser le tag <annotation-driven> de l'espace de nommage tx pour préciser à Spring que les annotations sont utilisées pour la définition des transactions.

Son attribut transaction-manager permet de préciser l'identifiant du bean qui encapsule le gestionnaire de transactions (TransactionManager) utilisé pour gérer les transactions : son utilisation n'est obligatoire que si l'id du gestionnaire de transactions est différent de "transactionManager".

Exemple :
<beans>
  <bean id="txManager" 
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="datasource" ref="dataSource" 
  </bean>

  <tx:annotation-driven transaction-manager="txManager"/> 

  <!-- ... -->
</beans>

La définition des transactions avec une annotation est plus simple à mettre en oeuvre, car il suffit d'annoter chaque méthode ou classe concernée avec @Transactional au lieu de la définir par des expressions régulières dans le fichier de configuration.

L'annotation org.springframework.transaction.annotation.Transactionnal s'utilise sur une classe ou une méthode. Sur une classe, elle s'applique automatiquement sur toutes les méthodes publiques de la classe.

L'annotation @Transactional possède plusieurs attributs :

  • propagation : précise le mode de propagation de la transaction grâce à une énumération de type Propagation. La valeur par défaut est Propagation.REQUIRED
  • readonly : booléen qui précise de façon informative au système de gestion des transactions sous-jacent si la transaction est en lecture seule (true) ou si elle effectue des mises à jour (false)
  • isolation : précise le niveau d'isolation de la transaction grâce à une énumération de type Isolation. La valeur par défaut est Isolation.DEFAULT
  • timeout : entier qui précise le timeout de la transaction
Exemple :
package com.jmdoudoux.test.spring.service;

@Service("personneService")
@Transactional
public class PersonneServiceImpl implements PersonneService {
  //... 

  @Transactional(readOnly=true)
  public List<Personne> findAll() throws ServiceException {
    //...
  }

  //... 
}

Il est fortement recommandé d'utiliser l'annotation @Transactional sur des classes et non sur des interfaces.

L'avantage de mettre en oeuvre les transactions par AOP est qu'il n'est pas nécessaire d'utiliser une API de Spring dans le code pour mettre en oeuvre les transactions. La mise en oeuvre reste aussi la même quelque soit l'implémentation du gestionnaire de transactions utilisée : la seule chose qui change c'est la configuration du transaction manager.

 

86.4.1. L'utilisation de l'annotation @Transactional

L'annotation @Transactional permet de délimiter une transaction (entre le début et la fin de la méthode) et de définir le comportement transactionnel d'une méthode.

L'annotation @Transactional possède plusieurs attributs :

Nom de l'attribut

Rôle

Valeur par défaut

propagation

mode de propagation de la transaction

PROPAGATION_REQUIRED

isolation

niveau d'isolation de la transaction

ISOLATIONDEFAULT

read-write

indique si la transaction est en lecture seule (false) ou lecture/écriture(true)

true

timeout

 

valeur par défaut de l'implémentation du système de gestion des transactions utilisé

rollbackFor

ensemble d'exceptions héritant de Throwable qui provoquent un rollback de la transaction si elles sont levées durant les traitements

 

rollbackForClassname

ensemble de noms de classes héritant de Throwable pour lesquelles un rollback est effectué si des exceptions sont levées pendant les traitements

 

noRollbackFor

ensemble d'exceptions héritant de Throwable qui ne provoquent pas un rollback de la transaction si elles sont levées durant les traitements

 

noRollbackForClassname

ensemble de noms de classes héritant de Throwable pour lesquelles la levée d'une exception ne provoquera pas de rollback

 

La simple utilisation de l'annotation @Transactional ne suffit pas car il ne représente que des métadonnées : il faut obligatoirement utiliser le tag <tx:annotation-driven> dans la configuration pour permettre à Spring d'ajouter les traitements relatifs aux aspects transactionaux sur les méthodes annotées.

Le tag <tx:annotation-driven> possède plusieurs attributs :

Nom de l'attribut

Rôle

Valeur par défaut

transaction-manager

nom du bean qui encapsule le gestionnaire de transactions (obligatoire uniquement si le nom ne correspond pas à la valeur par défaut)

transaction-manager

mode

les valeurs possibles sont proxy (utilisation de proxys) et aspectj (tissage des aspects avec AspectJ)

proxy

proxy-target-class

Permet de préciser le type de proxy utilisé (true : proxy reposant sur les interfaces, false : proxy reposant sur les classes). Ne doit être utilisé que si le mode est proxy

False

order

Permet de définir l'ordre des traitements exécutés sur les beans annotés avec @Transactional

Ordered.LOWEST PRECEDENCE


Attention : le tag <tx:annotation-driven> ne recherche les beans annotés avec @Transactional que dans le contexte dans lequel il est défini.

L'annotation @Transactional peut être utilisée sur une classe ou sur une méthode. Utilisée sur une classe, elle s'applique par défaut sur toutes les méthodes public de la classe sauf si la méthode est elle-même annotée avec @Transactional. Dans ce cas, c'est l'annotation sur la méthode qui est utilisée.

Exemple :
@Transactional(readOnly = true)
public class PersonneServicelmpl implements PersonneService { 
  public Personne getParld(long id) {
    // traitements de la methode
  }

  @Transactional(readOnly = false, propagation = Propagation.REQUIRESNEW) 
  public void modifier(Personne personne) {
    // traitements de la methode
  }
}

Seules les méthodes public doivent être annotées avec @Transactional lors de l'utilisation de proxys. Si des méthodes package-friendly, protected ou private sont annotées avec @Transactional, aucune erreur n'est produite à la compilation mais ces méthodes seront ignorées lors de l'utilisation des proxys.

Il est fortement recommandé de n'utiliser l'annotation @Transactional que dans des classes concrètes surtout dans le mode par défaut, le mode proxy.

Attention : dans le mode proxy, seules les invocations de méthodes depuis d'autres classes seront transactionnelles. Les invocations d'une méthode de la classe par une autre méthode de la classe ne sont pas transactionnelles même si la méthode invoquée est annotée avec @Transactional car ces invocations ne sont pas interceptées par le proxy.

Dans ce cas, il faut utiliser le mode AspectJ pour permettre un tissage des classes avec les aspects relatifs à la gestion des transactions pour les méthodes annotées. L'utilisation de ce mode requiert que la bibliothèque spring-aspects.jar soit ajoutée au classpath et le tissage (load-time weaving ou compile-time weaving) soit activé.

 

86.4.2. Le support de @Transactional par AspectJ

Il est possible d'utiliser le tissage d'aspects d'AspectJ pour intégrer le bytecode requis par le traitement des annotations @Transactional plutôt que d'utiliser des proxys gérés par le conteneur.

L'aspect à tisser est org.springframework.transaction.aspectj.AnnotationTransactionAspect contenu dans la bibliothèque spring-aspects.jar.

Il faut utiliser le tag <tx.annotation-driven> dans la configuration du contexte avec l'attribut mode="aspectj"

Le tissage peut se faire à la compilation ou au runtime.

L'annotation @Transactional peut alors être utilisée sur n'importe quelle méthode quelle que soit sa visibilité.

 

86.4.3. Un exemple de déclaration des transactions avec des annotations

L'exemple de cette section va utiliser Spring 3, AspectJ (en mode Load Time Weaving), JavaDB (en mode client/serveur), log4J.

La déclaration des transactions en utilisant les annotations est plus simple à mettre en oeuvre que la déclaration dans la configuration.

Il suffit d'utiliser l'annotation @Transactional sur les méthodes ou sur les classes concernées.

Exemple :
package com.jmdoudoux.test.spring.service;

import java.util.List;

import com.jmdoudoux.test.spring.entite.Personne;

public interface PersonneService {
  void ajouter(Personne personne);
  Personne getParId(long id);
  List<Personne> getTous();
  void modifier(Personne personne);
} 

Le service implémente l'interface ci-dessus.

Exemple :
package com.jmdoudoux.test.spring.service;

import java.util.List;

import org.springframework.transaction.annotation.Transactional;

import com.jmdoudoux.test.spring.entite.Personne;

public class PersonneServiceImpl implements PersonneService {

  @Override
  @Transactional
  public void ajouter(final Personne personne) {
    throw new UnsupportedOperationException();
  }

  @Override
  @Transactional(readOnly = true)
  public Personne getParId(final long id) {
    throw new UnsupportedOperationException();
  }

  @Override
  @Transactional(readOnly = true)
  public List<Personne> getTous() {
    throw new UnsupportedOperationException();
  }

  @Override
  @Transactional
  public void modifier(final Personne personne) {
    throw new UnsupportedOperationException();
  }
}

Dans cette implémentation, toutes les méthodes transactionnelles lèvent une exception, ce qui permet de tester le rollback de la transaction.

La configuration du contexte est simplifiée car il suffit de déclarer le gestionnaire de transactions à utiliser et d'utiliser le tag <tx:annotation-driven>.

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:aop="http:/www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

        <tx:annotation-driven mode="aspectj" transaction-manager="txManager" />

        <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
               destroy-method="close">
               <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
               <property name="url" value="jdbc:derby://localhost/MaBaseDeTest" />
        </bean>
        
        <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
               <property name="dataSource" ref="datasource" />
        </bean>

  <bean id="personneService" class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />
</beans> 

L'attribut transaction-manager du tag <tx:annotation-driven> permet de préciser l'instance du gestionnaire de transactions à utiliser. Cet attribut peut être facultatif si le nom du bean du gestionnaire de transactions est "transactionManager".

Exemple :
package com.jmdoudoux.test.spring;

import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.jmdoudoux.test.spring.entite.Personne;
import com.jmdoudoux.test.spring.service.PersonneService;

public class MonApp {
  private static Logger LOGGER = Logger.getLogger(MonApp.class);
  
  public static void main(final String[] args) throws Exception {
    LOGGER.info("Debut de l'application");
    
    ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
        new String[] { "appContext.xml" });
    PersonneService personneService = (PersonneService) appContext
        .getBean("personneService");
    LOGGER.info("Debutinvocation du service");
    try {
      personneService.ajouter(newPersonne());
    } catch (Exception e) {
      LOGGER.error("exception" + e.getClass().getName() + " interceptee");
    }
    LOGGER.info("Fin invocation du service");
    LOGGER.info("Fin de l'application");
  }
}

L'application de test charge le contexte, lui demande un instance du service et invoque sa méthode ajouter().

Le niveau de traces dans le fichier de configuration de Log4J est configuré sur debug pour permettre de voir le détail des actions réalisées par Spring pour gérer les transactions.

Exemple :
<?xml version="1.0" encoding= "UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration
xmlns:log4j="http://jakarta.apache.org/log4j/">
  <appender name="stdout" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %p [%c] - %m%n"/>
    </layout>
  </appender>
 
  <root> 
    <priority value="debug"/>
      <appender-ref ref="stdout"/>
  </root>
</log4j:configuration>

Le classpath de l'application contient les bibliothèques requises :

org.springframework.transaction-3.0.5.RELEASE.jar, org.springframework.aop-3.0.5.RELEASE.jar, org.springframework.asm-3.0.5.RELEASE.jar, org.springframework.aspects-3.0.5.RELEASE.jar, org.springframework.beans-3.0.5.RELEASE.jar, org.springframework.context-3.0.5.RELEASE.jar, org.springframework.core-3.0.5.RELEASE.jar, org.springframework.expression-3.0.5.RELEASE.jar, com.springsource.org.apache.commons.logging-1.1.1.jar, com.springsource.org.aopalliance-1.0.0.jar, commons-dbcp-1.4.jar, com.springsource.org.apache.commons.pool-1.5.3.jar, org.springframework.jdbc-3.0.5.RELEASE.jar, derbyclient.jar, derbynet.jar, org.springframework.instrument-3.0.5.RELEASE.jar, spring-aspects-3.0.5.RELEASE.jar, log4j-1.2.16.jar

La JVM est lancée avec l'option -javaagent:C:/java/api/aspectjweaver/aspectjweaver-1.6.1.jar pour activer l'agent AspectJ qui va se charger de tisser les aspects, notamment ceux concernant l'annotation @Transactional, au runtime.

Lors de l'exécution de l'application, l'appel de la méthode du service provoque un rollback puisqu'une exception de type runtime est levée durant ses traitements.

Résultat :
2011-04-26 22:17:48,453  INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
2011-04-26 22:17:50,187  INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
...
2011-04-26 22:17:50,218 DEBUG [org.springframework.transaction.annotation
.AnnotationTransactionAttributeSource] Adding transactional method 'ajouter' with attribute: 
PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2011-04-26 22:17:50,218 DEBUG [org.springframework.beans.factory.support.
DefaultListableBeanFactory] Returning cached instance of singleton bean 'txManager'
2011-04-26 22:17:50,250 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Creating new transaction with name [com.jmdoudoux.test.spring.
service.PersonneServiceImpl.ajouter]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2011-04-26 22:17:51,296 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Acquired Connection [jdbc:derby://localhost:1527/MaBaseDeTest, 
UserName=APP, Apache Derby Network Client JDBC Driver] for JDBC transaction
2011-04-26 22:17:51,328 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Switching JDBC Connection [jdbc:derby://localhost:1527/
MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver] to manual commit
2011-04-26 22:17:51,328 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Initiating transaction rollback
2011-04-26 22:17:51,328 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Rolling back JDBC transaction on Connection [jdbc:derby:
//localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver]
2011-04-26 22:17:51,328 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Releasing JDBC Connection [jdbc:derby://localhost:1527/
MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver] after transaction
2011-04-26 22:17:51,328 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils]
 Returning JDBC Connection to DataSource
2011-04-26 22:17:51,328 ERROR [com.jmdoudoux.test.spring.MonApp]
 exception java.lang.UnsupportedOperationException interceptee
2011-04-26 22:17:51,328  INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-04-26 22:17:51,328  INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application

Si la méthode du service ne lève pas d'exception durant son invocation, la transaction est validée par un commit.

Résultat :
2011-04-26 22:25:17,484  INFO
[com.jmdoudoux.test.spring.MonApp] Debut de l'application
...
2011-04-26 22:25:19,250  INFO
[com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-04-26 22:25:19,296 DEBUG
[org.springframework.transaction.annotation.AnnotationTransactionAttributeSource]
Adding transactional method 'ajouter' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT;
''
2011-04-26 22:25:19,296 DEBUG
[org.springframework.beans.factory.support.DefaultListableBeanFactory]
Returning cached instance of singleton bean 'txManager'
2011-04-26 22:25:19,312 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Creating new transaction with name
[com.jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter]:
PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2011-04-26 22:25:20,390 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Acquired Connection [jdbc:derby://localhost:1527/
MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver] for JDBC transaction
2011-04-26 22:25:20,421 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Switching
JDBC Connection [jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache
Derby Network Client JDBC Driver] to manual commit
2011-04-26 22:25:20,421 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Initiating
transaction commit
2011-04-26 22:25:20,421 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Committing
JDBC transaction on Connection [jdbc:derby://localhost:1527/MaBaseDeTest,
UserName=APP, Apache Derby Network Client JDBC Driver]
2011-04-26 22:25:20,421 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Releasing
JDBC Connection [jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache
Derby Network Client JDBC Driver] after transaction
2011-04-26 22:25:20,421 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils]
Returning JDBC Connection to DataSource
2011-04-26 22:25:20,421  INFO
[com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-04-26 22:25:20,421  INFO
[com.jmdoudoux.test.spring.MonApp] Fin de l'application

 

86.5. La gestion du rollback des transactions

Spring utilise des règles particulières reposant sur les exceptions pour effectuer un rollback, au besoin, de la transaction. Par défaut, un rollback est effectué si une exception de type unchecked est levée dans les traitements de la transaction.

Ainsi par défaut, une transaction est annulée (rollback) si une exception de type RuntimeException ou Error est levée durant les traitements exécutés dans le contexte de la transaction. Donc par défaut, une transaction n'est pas annulée si une exception de type checked est levée dans les traitements.

Spring permet cependant une configuration fine des types d'exceptions qui vont provoquer un rollback de la transaction : ces règles peuvent être adaptées dans la déclaration de la transaction en précisant quelles exceptions provoquent un rollback ou non.

Il est aussi possible de forcer un rollback de la transaction par programmation en invoquant la méthode setRollback0nly() sur l'objet de type TransactionStatus.

 

86.5.1. La gestion du rollback dans la configuration

Si les transactions sont définies dans la configuration du contexte, les attributs rollback-for et no-rollback-for du tag <tx:method> permettent respectivement de préciser l'exception ou les types d'exceptions qui vont provoquer un rollback ou non.

Exemple :
<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="MonException"/> 
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice> 

Dans cet exemple, un rollback de la transaction sera exécuté par Spring si une exception de type MonException est levée durant les traitements du contexte transactionnel.

Exemple :
<tx:advice id="txAdvice" transaction-manager="txManager"> 
  <tx:attributes>
    <tx:method name="get*" read-only="true" no-rollback-for="MonAutreException"/>
    <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

Dans cet exemple, si seule une exception de type MonAutreException est levée durant les traitements, le commit de la transaction sera exécuté par Spring.

Exemple :
<tx:advice id="txAdvice">
  <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="MonException"/>
  </tx:attributes>
</tx:advice> 

Dans l'exemple ci-dessus, seule l'exception MonException ne va pas provoquer un rollback de la transaction.

 

86.5.2. La gestion du rollback via l'API

Il est aussi possible de forcer le rollback de la transaction par programmation en utilisant l'API.

Exemple :
public void maMethode() {
  try {
    // traitements
  } catch (MonException ex) { 
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  }
}

Cette solution impose d'utiliser l'API de Spring ce qui n'est pas la meilleure solution : il est préférable dans la mesure du possible d'utiliser l'approche déclarative.

 

86.5.3. La gestion du rollback avec les annotations

L'annotation @Transactional possède plusieurs attributs permettant de gérer finement un éventuel rollback de la transaction.

L'attribut rollbackFor permet de préciser un tableau d'exceptions héritant de Throwable qui provoquent un rollback de la transaction si elles sont levées durant les traitements.

L'attribut rollbackForClassname permet de préciser un tableau de noms de classes héritant de Throwable qui si elles sont levées provoqueront un rollback de la transaction.

L'attribut noRollbackFor permet de préciser un tableau d'exceptions héritant de Throwable qui ne provoquent pas un rollback de la transaction si elles sont levées durant les traitements.

L'attribut noRollbackForClassname permet de préciser un tableau de noms de classes héritant de Throwable qui si elles sont levées ne provoqueront pas un rollback de la transaction.

Ces quatre attributs permettent de configurer de façon précise les conditions selon lesquelles un rollback de la transaction sera fait par les traitements de l'annotation @Transactional.

 

86.6. La mise en oeuvre d'aspects sur une méthode transactionnelle

Les transactions sont mises en oeuvre grâce à l'AOP mais il est aussi possible d'utiliser l'AOP pour ses propres besoins sur des méthodes transactionnelles.

Spring offre un moyen de configurer l'ordre d'exécution de ces aspects en implémentant l'interface Ordered.

Exemple :
package com.jmdoudoux.test.spring.aspect;

import org.aspectj.lang.ProceedingJoinPoint; 
import org.springframework.util.StopWatch; 
import org.springframework.core.Ordered;

public class MonitoringPerf implements Ordered { 
  private int order;

  public int getOrder() { 
    return this.order; 
  }

  public void setOrder(int order) { 
    this.order = order;
  }
 
  public Object executer(ProceedingJoinPoint call) throws Throwable {
    Object returnValue;
    StopWatch clock = new StopWatch(getClass().getName());
    try {
      clock.start(call.toShortString());
      returnValue = call.proceed(); 
    } finally {
      clock.stop();
      System.out.println(clock.prettyPrint());
    }
    return returnValue; 
  }
}

Il faut définir l'aspect dans le fichier de configuration du contexte et préciser l'ordre d'invocation des aspects.

Le fichier de configuration si on déclare les transactions par annotations :

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <tx:annotation-driven mode="aspectj"
    transaction-manager="txManager" order="99" />

  <aop:config>
    <aop:aspect id="monitorerPerfAspect" ref="monitorerPerf">
      <aop:pointcut id="methodeService"
        expression="execution(* com.jmdoudoux.test.spring.service.*ServiceImpl.*(..))" />
      <aop:around method="executer" pointcut-ref="methodeService" />
    </aop:aspect>
  </aop:config>

  <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
    <property name="url" value="jdbc:derby://localhost/MaBaseDeTest" />
  </bean>

  <bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource" />
  </bean>

  <bean id="monitorerPerf" class="com.jmdoudoux.test.spring.aspect.MonitorerPerf">
    <property name="order" value="1" />
  </bean>

  <bean id="personneService" 
    class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />

</beans>

L'ordre d'invocation des aspects est défini grâce à la valeur fournie aux attributs order du bean qui encapsule l'aspect et de l'attribut order du tag <annotation-driven>.

Le fichier de configuration si on déclare les transactions dans le fichier de configuration :

Exemple :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd 
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd 
http://www.springframework.org/schema/aop 
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

  <tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
      <tx:method name="get*" read-only="true" />
      <tx:method name="*" />
    </tx:attributes>
  </tx:advice>

  <aop:config>
    <aop:pointcut id="personneServiceOperation"
      expression="execution(* com.jmdoudoux.test.spring.service.PersonneService.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="personneServiceOperation" 
      order="99" />

    <aop:aspect id="monitorerPerfAspect" ref="monitorerPerf">
      <aop:around method="executer" pointcut-ref="personneServiceOperation" />
    </aop:aspect>

  </aop:config>

  <bean id="datasource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver" />
    <property name="url" value="jdbc:derby://localhost/MaBaseDeTest" />
  </bean>

  <bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="datasource" />
  </bean>

  <bean id="monitorerPerf" class="com.jmdoudoux.test.spring.aspect.MonitorerPerf">
    <property name="order" value="1" />
  </bean>

  <bean id="personneService" 
    class="com.jmdoudoux.test.spring.service.PersonneServiceImpl" />

</beans> 

L'ordre d'invocation des aspects est défini grâce à la valeur fournie aux attributs order du bean qui encapsule l'aspect et de l'advisor qui concerne la gestion des transactions.

L'exécution de l'application affiche le temps d'exécution suite à l'invocation de la méthode

Résultat :
2011-04-28 22:27:47,375  INFO [com.jmdoudoux.test.spring.MonApp] Debut de l'application
...
2011-04-28 22:27:49,578  INFO [com.jmdoudoux.test.spring.MonApp] Debut invocation du service
2011-04-28 22:27:49,593 DEBUG [org.springframework.beans.factory.support.
DefaultListableBeanFactory] Returning cached instance of singleton bean 'monitorerPerf'
2011-04-28 22:27:49,640 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Creating new transaction with name 
[com.jmdoudoux.test.spring.service.PersonneServiceImpl.ajouter]:
 PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2011-04-28 22:27:50,687 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Acquired Connection 
[jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client 
JDBC Driver] for JDBC transaction
2011-04-28 22:27:50,703 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Switching JDBC Connection 
[jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client 
JDBC Driver] to manual commit
2011-04-28 22:27:50,703 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Initiating transaction commit
2011-04-28 22:27:50,703 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Committing JDBC transaction on Connection [jdbc:derby:
//localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client JDBC Driver]
2011-04-28 22:27:50,703 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Releasing JDBC Connection 
[jdbc:derby://localhost:1527/MaBaseDeTest, UserName=APP, Apache Derby Network Client
 JDBC Driver] after transaction
2011-04-28 22:27:50,703 DEBUG [org.springframework.jdbc.datasource.DataSourceUtils]
 Returning JDBC Connection to DataSource
2011-04-28 22:27:50,703 DEBUG [com.jmdoudoux.test.spring.aspect.MonitorerPerf] 
temps d'execution : StopWatch 
'com.jmdoudoux.test.spring.aspect.MonitorerPerf': running time (millis) = 1094
-----------------------------------------
ms     %     Task name
-----------------------------------------
01094  100 %  execution(PersonneService.ajouter(..))

2011-04-28 18:27:50,703  INFO [com.jmdoudoux.test.spring.MonApp] Fin invocation du service
2011-04-28 18:27:50,703  INFO [com.jmdoudoux.test.spring.MonApp] Fin de l'application

 

86.7. L'utilisation des transactions via l'API

Pour la mise en oeuvre des transactions par programmation, Spring propose deux solutions :

  • utiliser la classe TransactionTemplate
  • utiliser directement une implémentation de l'interface PlatformTransactionManager

L'utilisation de ces deux solutions lie fortement le code de l'application avec Spring puisqu'elles utilisent des API dédiées. Elles ne sont donc à utiliser que pour des besoins très spécifiques et une solution déclarative est préférable.

 

86.7.1. L'utilisation de la classe TransactionTemplate

Le principe de la classe TransactionTemplate repose sur l'utilisation de callbacks comme pour les autres templates Spring.

Il faut écrire une implémentation de TransactionCallback, généralement sous la forme d'une classe anonyme interne qui va contenir les traitements à exécuter dans le contexte transactionnel.

Il suffit alors de passer une instance de cette implémentation en paramètre de la méthode execute() de la classe TransactionTemplate.

Exemple :
package com.jmdoudoux.test.spring.service;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.jmdoudoux.test.spring.entite.Personne;

public class MonServiceImpl implements MonService {

  private final TransactionTemplate transactionTemplate;

  public MonServiceImpl(final PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  protected boolean maMethode(final Personne personne) {
    // traitement de la methode
    return true;
  }

  public Object maMethodeTransactionelle(final Personne personne) {
    return transactionTemplate.execute(new TransactionCallback() {

      public Object doInTransaction(final TransactionStatus status) {
        return maMethode(personne);
      }
    });
  }
}

L'instance de TransactionTemplate est utilisée par toutes les méthodes d'instances.

L'injection de l'instance du gestionnaire de transactions se fait en passant par le constructeur.

Si la méthode transactionnelle ne renvoie aucun résultat, il faut utiliser la classe TransactionCallbackWithoutResult.

Exemple :
package com.jmdoudoux.test.spring.service;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.jmdoudoux.test.spring.entite.Personne;

public class MonServiceImpl implements MonService {

  private final TransactionTemplate transactionTemplate;

  public MonServiceImpl(final PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  protected void maMethode(final Personne personne) {
    // traitement de la methode
  }

  public Object maMethodeTransactionelle(final Personne personne) {
    return transactionTemplate.execute(new TransactionCallbackWithoutResult() {

      public void doInTransactionWithoutResult(final TransactionStatus status) {
        maMethode(personne);
      }
    });
  }
}

Il est possible de demander explicitement l'annulation de la transaction dans le code du callback en invoquant la méthode setRollbackOnly() de l'instance de TransactionStatus fournie en paramètre.

Exemple :
package com.jmdoudoux.test.spring.service;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.jmdoudoux.test.spring.entite.Personne;

public class MonServiceImpl implements MonService {

  private final TransactionTemplate transactionTemplate;

  public MonServiceImpl(final PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
  }

  protected boolean maMethode(final Personne personne) {
    // traitement de la methode
    return true;
  }

  public Object maMethodeTransactionelle(final Personne personne) {
    return transactionTemplate.execute(new TransactionCallback() {

      @Override
      public Object doInTransaction(final TransactionStatus status) {
        boolean resultat = false;
        try {
          resultat = maMethode(personne);
        } catch (MonException ex) {
          status.setRollbackOnly();
        }
        return resultat;
      }
    });
  }
} 

La classe TransactionTemplate possède plusieurs méthodes pour permettre de configurer le contexte transactionnel.

Exemple :
package com.jmdoudoux.test.spring.service;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

import com.jmdoudoux.test.spring.entite.Personne;

public class MonServiceImpl implements MonService {

  private final TransactionTemplate transactionTemplate;

  public MonServiceImpl(final PlatformTransactionManager transactionManager) {
    this.transactionTemplate = new TransactionTemplate(transactionManager);
    this.transactionTemplate
        .setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
    this.transactionTemplate.setTimeout(60);

  }

  // ...
}

Les instances de TransactionTemplate sont threadsafe mais elles contiennent la configuration. Il est donc possible de partager une instance de TransactionTemplate mais il est nécessaire d'avoir autant d'instances que de configurations différentes.

Il est donc possible de déclarer un bean de type TransactionTemplate et de le paramétrer dans la configuration puis de l'injecter dans les services qui en ont besoin.

Exemple :
  <bean
id="monTransactionTemplate"
   
class="org.springframework.transaction.support.TransactionTemplate">
   
<property name="isolationLevelName"
value="ISOLATION_READ_UNCOMMITTED" />
   
<property name="timeout" value="30" />
 
</bean>

 

86.7.2. L'utilisation directe d'un PlatformTransactionManager

Par programmation, il est possible d'utiliser directement une instance de type PlatformTransactionManager pour gérer les transactions.

Dans le bean, il faut permettre une injection de l'instance de PlatformTransactionManager par Spring.

L'interface TransactionDefinition définit la configuration d'une transaction.

Le plus simple est de créer une instance de la classe DefaultTransactionDefinition puis d'invoquer sa méthode setName() pour lui attribuer un nom et invoquer toutes les méthodes utiles pour configurer la transaction.

La méthode getTransaction() qui attend en paramètre une instance de TransactionDefinition renvoie une instance de l'interface TransactionStatus.

L'interface TransactionStatus définit le statut d'une transaction. Elle propose plusieurs méthodes :

Méthode

Rôle

boolean hasSavepoint()

Renvoie un booléen qui précise si la transaction a été créée comme englobée dans une transaction possédant un savepoint

boolean isCompleted()

Renvoie un booléen qui précise si la transaction est terminée (par un rollback ou un commit)

boolean isNewTransaction()

Renvoie un booléen qui précise si la transaction est nouvelle

boolean isRollbackOnly()

Renvoie un booléen qui précise si la transaction est marquée comme devant être annulée

void setRollbackOnly()

Demande l'annulation de la transaction


Exemple :
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setName("MaTransaction");
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def);
    try {
      // les traitements
    } catch (MonException ex) {
      txManager.rollback(status);
      throw ex;
    }
    txManager.commit(status);

 

86.8. L'utilisation d'un gestionnaire de transactions reposant sur JTA

Spring permet d'utiliser un gestionnaire de transactions implémentant l'API JTA, généralement fourni par un serveur d'applications.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]