Introduction aux Transactions Spring
Une transaction représente un ensemble logique d'opérations où chaque unité doit soit réussir entièrement, soit échouer complètement, garantissant ainsi l'atomicité des données. Spring Framework fournit une abstraction puissante pour gérer ces transactions de manière transparente, indépendamment de l'environnement sous-jacent.
Interfaces Clés de l'API Transactionnelle
L'infrastructure transactionnelle de Spring repose sur trois interfaces fondamentales :
- PlatformTransactionManager : Il s'agit de l'interface centrale qui orchestre la gestion des transactions. Selon la technologie de persistance utilisée, Spring fournit différentes implémentations, telles que
DataSourceTransactionManagerpour JDBC ouHibernateTransactionManagerpour Hibernate. - TransactionDefinition : Cette interface encapsule les métadonnées de la transaction, notamment le niveau d'isolation, le délai d'expiration (timeout), le comportement de propagation et l'attribut de lecture seule (read-only).
- TransactionStatus : Elle représente l'état d'exécution de la transaction en cours, permettant au gestionnaire de suivre le cycle de vie et de déclencher des validations (commit) ou des annulations (rollback).
Lors de l'exécution, le PlatformTransactionManager s'appuie sur le TransactionDefinition pour initialiser la transaction, puis met à jour le TransactionStatus au fur et à mesure de l'avancement des opérations.
Comportements de Propagation
La propagation définit comment les transactions se comportent lorsqu'une méthode transactionnelle est appelée par une autre. Ce mécanisme est crucial pour gérer les appels imbriqués entre les différentes couches de service.
Intégration dans une transaction exsitante
- PROPAGATION_REQUIRED : C'est le comportement par défaut. Si une transaction est déjà active, la méthode s'y joint. Sinon, une nouvelle transaction est créée.
- PROPAGATION_SUPPORTS : La méthode s'exécute dans la transaction courante si elle existe, ou s'exécute sans transaction dans le cas contraire.
- PROPAGATION_MANDATORY : Exige qu'une transaction soit déjà active. Si aucune transaction n'est trouvée, une exception est levée.
Exécution hors de la transaction cournate
- PROPAGATION_REQUIRES_NEW : Suspend la transaction active (s'il y en a une) et crée une nouvelle transaction indépendante pour la méthode actuelle.
- PROPAGATION_NOT_SUPPORTED : Suspend la transaction courante et exécute la méthode sans aucun contexte transactionnel.
- PROPAGATION_NEVER : Interdit la présence d'une transaction active. Si une transaction existe, une exception est immédiatement levée.
Transactions imbriquées
- PROPAGATION_NESTED : Si une transaction est active, la méthode s'exécute dans une transaction imbriquée (nested) avec un point de sauvegarde (savepoint). En cas d'erreur, le rollback peut être partiel (jusqu'au savepoint) sans affecter la transaction principale.
Mise en Place de la Gestion Transactionnelle
Spring permet de configurer les transactions de manière déclarative via XML ou par annotations. Prenons l'exemple d'un service de transfert bancaire.
Approche 1 : Configuration Déclarative via XML
Cette méthode utilise AOP pour tisser le comportement transactionnel autour des méthodes ciblées.
<?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.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="bankService" class="com.example.bank.service.impl.BankServiceImpl"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dbDataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="executeTransfer" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceOperations" expression="execution(* com.example.bank.service.impl.BankServiceImpl.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperations"/>
</aop:config>
</beans>
Approche 2 : Configuration par Annotations
L'approche par annotations est plus concise. Il suffit d'activer le support des annotations dans le fichier de configuration Spring.
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dbDataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
Ensuite, l'annotation @Transactional est appliquée directement sur la classe ou la méthode. Appliquée sur une classe, elle s'applique à toutes ses méthodes publiques. Appliquée sur une méthode, elle cible spécifiquement cette opération.
package com.example.bank.service.impl;
import com.example.bank.repository.AccountRepository;
import com.example.bank.service.BankService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankServiceImpl implements BankService {
private final AccountRepository accountRepository;
@Autowired
public BankServiceImpl(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Override
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void executeTransfer(String sourceAccountId, String targetAccountId, double amount) {
accountRepository.debit(sourceAccountId, amount);
accountRepository.credit(targetAccountId, amount);
}
}