Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | 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 :
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 :
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 :
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.
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 :
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(*fr.jmdoudoux.dej.spring.service.*ServiceImpl.*(..))" />
<aop:advisor advice-ref="serviceTxAdvice" pointcut-ref="serviceMethodes" />
</aop:config>
<aop:config>
<aop:pointcut id="daoMethodes"
expression="execution(*fr.jmdoudoux.dej.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(*
fr.jmdoudoux.dej.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(* fr.jmdoudoux.dej.spring.service.*Service.*(..))" />
<aop:pointcut id="noTxServiceOperation"
expression="execution(* fr.jmdoudoux.dej.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="fr.jmdoudoux.dej.spring.service.PersonneServiceImpl" />
<bean id="personneCache" class="fr.jmdoudoux.dej.spring.service.cache.PersonneCache" />
</beans>
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 fr.jmdoudoux.dej.spring.service;
import java.util.List;
import fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.spring.service;
import java.util.List;
import fr.jmdoudoux.dej.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(* fr.jmdoudoux.dej.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="fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.spring;
import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import fr.jmdoudoux.dej.spring.entite.Personne;
import fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.MonApp] Debut invocation du service
2011-04-28 22:16:10,031 DEBUG [org.springframework.jdbc.datasource.
DataSourceTransactionManager] Creating new transaction with name
[fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.MonApp]
exception java.lang.UnsupportedOperationException interceptee
2011-04-28 22:16:11,109 INFO [fr.jmdoudoux.dej.spring.MonApp] Fin invocation du service
2011-04-28 22:16:11,109 INFO [fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.spring.MonApp] Debut de l'application
...
2011-04-28 22:18:07,625 INFO
[fr.jmdoudoux.dej.spring.MonApp] Debut invocation du service
2011-04-28 22:18:07,671 DEBUG
[org.springframework.jdbc.datasource.DataSourceTransactionManager] Creating new
transaction with name [fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.spring.MonApp] Fin invocation du service
2011-04-28 22:18:08,734 INFO
[fr.jmdoudoux.dej.spring.MonApp] Fin de l'application
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 :
Exemple : |
package fr.jmdoudoux.dej.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.
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-private, 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é.
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é.
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 fr.jmdoudoux.dej.spring.service;
import java.util.List;
import fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.spring.service;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import fr.jmdoudoux.dej.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="fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.spring;
import org.apache.log4j.Logger;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import fr.jmdoudoux.dej.spring.entite.Personne;
import fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.MonApp] Debut de l'application
2011-04-26 22:17:50,187 INFO [fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.MonApp]
exception java.lang.UnsupportedOperationException interceptee
2011-04-26 22:17:51,328 INFO [fr.jmdoudoux.dej.spring.MonApp] Fin invocation du service
2011-04-26 22:17:51,328 INFO [fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.spring.MonApp] Debut de l'application
...
2011-04-26 22:25:19,250 INFO
[fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.spring.MonApp] Fin invocation du service
2011-04-26 22:25:20,421 INFO
[fr.jmdoudoux.dej.spring.MonApp] Fin de l'application
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.
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.
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.
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.
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 fr.jmdoudoux.dej.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(* fr.jmdoudoux.dej.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="fr.jmdoudoux.dej.spring.aspect.MonitorerPerf">
<property name="order" value="1" />
</bean>
<bean id="personneService"
class="fr.jmdoudoux.dej.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(* fr.jmdoudoux.dej.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="fr.jmdoudoux.dej.spring.aspect.MonitorerPerf">
<property name="order" value="1" />
</bean>
<bean id="personneService"
class="fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.MonApp] Debut de l'application
...
2011-04-28 22:27:49,578 INFO [fr.jmdoudoux.dej.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
[fr.jmdoudoux.dej.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 [fr.jmdoudoux.dej.spring.aspect.MonitorerPerf]
temps d'execution : StopWatch
'fr.jmdoudoux.dej.spring.aspect.MonitorerPerf': running time (millis) = 1094
-----------------------------------------
ms % Task name
-----------------------------------------
01094 100 % execution(PersonneService.ajouter(..))
2011-04-28 18:27:50,703 INFO [fr.jmdoudoux.dej.spring.MonApp] Fin invocation du service
2011-04-28 18:27:50,703 INFO [fr.jmdoudoux.dej.spring.MonApp] Fin de l'application
Pour la mise en oeuvre des transactions par programmation, Spring propose deux solutions :
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.
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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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 fr.jmdoudoux.dej.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>
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);
Spring permet d'utiliser un gestionnaire de transactions implémentant l'API JTA, généralement fourni par un serveur d'applications.
|
La suite de cette section sera développée dans une version future de ce document
|
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |