0% ont trouvé ce document utile (0 vote)
0 vues23 pages

Transaction Spring

Ce document explique les mécanismes transactionnels dans le Spring Framework, en se concentrant sur les niveaux d'isolation et les stratégies de propagation. Il aborde les problématiques rencontrées dans les applications d'entreprise et fournit des solutions pratiques pour gérer les transactions. Destiné aux développeurs Java, il propose des bonnes pratiques et des exemples concrets pour une mise en œuvre efficace des transactions.

Transféré par

krachelfahd
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
0 vues23 pages

Transaction Spring

Ce document explique les mécanismes transactionnels dans le Spring Framework, en se concentrant sur les niveaux d'isolation et les stratégies de propagation. Il aborde les problématiques rencontrées dans les applications d'entreprise et fournit des solutions pratiques pour gérer les transactions. Destiné aux développeurs Java, il propose des bonnes pratiques et des exemples concrets pour une mise en œuvre efficace des transactions.

Transféré par

krachelfahd
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Spring Transactionnel : Comprendre l’Isolation

et la Propagation

Dr. BADR EL KHALYLY

18 novembre 2025

1
Résumé
Ce cours présente de manière détaillée et pédagogique les mécanismes transac-
tionnels dans Spring Framework, avec un accent particulier sur les niveaux d’isola-
tion et les stratégies de propagation. L’approche adoptée part des problématiques
concrètes rencontrées dans les applications d’entreprise pour introduire progressive-
ment les solutions offertes par Spring. Ce document s’adresse aux développeurs Java
souhaitant maîtriser la gestion des transactions dans leurs applications Spring.

2
Table des matières
1 Introduction 5
1.1 Qu’est-ce qu’une transaction ? . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Pourquoi avons-nous besoin de transactions ? . . . . . . . . . . . . . . . . . 5
1.3 Transactions et threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Problématiques liées aux transactions 6


2.1 Concurrence et accès simultanés . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.1 Lectures sales (Dirty Reads) . . . . . . . . . . . . . . . . . . . . . . 6
2.1.2 Lectures non reproductibles (Non-repeatable Reads) . . . . . . . . . 7
2.1.3 Lectures fantômes (Phantom Reads) . . . . . . . . . . . . . . . . . 7
2.2 Imbrication des transactions . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Solutions avec Spring : Niveaux d’isolation 7


3.1 DEFAULT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.2 READ_UNCOMMITTED . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.3 READ_COMMITTED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.4 REPEATABLE_READ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.5 SERIALIZABLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

4 Solutions avec Spring : Propagation des transactions 10


4.1 REQUIRED (Par défaut) . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.2 SUPPORTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.3 MANDATORY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
4.4 REQUIRES_NEW . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
4.5 NOT_SUPPORTED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.6 NEVER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
4.7 NESTED . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

5 Gestion des exceptions et rollback 15


5.1 Mécanisme de rollback dans Spring . . . . . . . . . . . . . . . . . . . . . . 15
5.2 Personnalisation du comportement de rollback . . . . . . . . . . . . . . . . 15
5.3 Rollback programmatique . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
5.4 Exceptions de rollback et transactions imbriquées . . . . . . . . . . . . . . 16

6 Mise en œuvre pratique 17


6.1 Configuration des transactions dans Spring . . . . . . . . . . . . . . . . . . 17
6.1.1 Configuration par annotations . . . . . . . . . . . . . . . . . . . . . 18
6.1.2 Configuration programmatique . . . . . . . . . . . . . . . . . . . . 18
6.2 Exemple complet : Système de transfert bancaire . . . . . . . . . . . . . . 19

7 Bonnes pratiques 20
7.1 Choisir le bon niveau d’isolation . . . . . . . . . . . . . . . . . . . . . . . . 20
7.2 Choisir la bonne stratégie de propagation . . . . . . . . . . . . . . . . . . . 21
7.3 Optimiser les performances . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
7.4 Gérer les exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

3
8 Cas d’utilisation avancés 21
8.1 Transactions distribuées . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
8.2 Transactions programmatiques avec TransactionTemplate . . . . . . . . . . 22
8.3 Tests avec transactions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

9 Conclusion 23

10 Exercices pratiques 23

11 Références 23

4
1 Introduction
1.1 Qu’est-ce qu’une transaction ?
Une transaction est une séquence d’opérations qui forme une unité logique de travail.
Les transactions sont régies par quatre propriétés fondamentales, souvent désignées par
l’acronyme ACID :
— Atomicité : Une transaction est traitée dans sa totalité ou pas du tout.
— Cohérence : Une transaction préserve la cohérence de la base de données.
— Isolation : Les transactions s’exécutent indépendamment les unes des autres.
— Durabilité : Une fois validée, une transaction persiste même en cas de panne.

1.2 Pourquoi avons-nous besoin de transactions ?


Dans les applications d’entreprise, nous devons souvent exécuter plusieurs opérations
comme une seule unité de travail. Par exemple, lors d’un transfert bancaire, nous devons
débiter un compte et créditer un autre. Si l’une de ces opérations échoue, nous devons
annuler toutes les modifications pour maintenir la cohérence des données.

Exemple concret
Imaginons une application bancaire où un client transfère 1000€ du compte A au
compte B. Cette opération implique deux étapes :
1. Débiter 1000€ du compte A
2. Créditer 1000€ sur le compte B
Si le système tombe en panne après la première étape mais avant la seconde, l’argent
disparaîtrait sans jamais atteindre le compte B. Les transactions permettent d’éviter
ce scénario.

1.3 Transactions et threads


Dans Spring, les transactions sont associées au thread d’exécution courant. Cette as-
sociation est fondamentale pour comprendre comment fonctionnent les transactions dans
un environnement multi-threads.

Point clé : Transactions liées aux threads


Spring utilise un mécanisme de stockage basé sur ThreadLocal pour associer la
transaction courante au thread d’exécution. Cela signifie que :
— Chaque thread possède sa propre transaction ou référence à une transaction
— Les transactions ne sont pas partagées entre les threads
— Si vous créez un nouveau thread à l’intérieur d’une méthode transactionnelle,
ce nouveau thread n’héritera pas du contexte transactionnel

1 @Service
2 public class MonService {
3

4 @Autowired
5 private AutreService autreService ;
6

5
7 @Transactional
8 public void m e t h o d e T r a n s a c t i o n n e l l e () {
9 // Cette o p r a t i o n fait partie de la transaction
10 entite . setValeur ( " nouvelle valeur " ) ;
11
12 // Ce thread s p a r n ’ h r i t e PAS de la transaction
13 new Thread (() -> {
14 // Cette o p r a t i o n n ’ est PAS dans la transaction principale
15 autreService . faireQu elqueCho se () ;
16 }) . start () ;
17 }
18 }

Problématique
Dans les applications web ou les applications avec des traitements asynchrones, la
gestion des transactions entre différents threads peut devenir complexe. Comment
garantir la cohérence des données lorsque plusieurs threads doivent participer à une
même unité logique de travail ?

Solution
Pour propager une transaction entre différents threads, vous pouvez utiliser :
— TransactionSynchronizationManager pour obtenir les ressources transaction-
nelles actuelles
— JTA (Java Transaction API) pour les transactions distribuées
— Des frameworks comme Spring Integration qui gèrent la propagation du
contexte

2 Problématiques liées aux transactions


2.1 Concurrence et accès simultanés
Dans un environnement multi-utilisateurs, plusieurs transactions peuvent tenter d’ac-
céder aux mêmes données simultanément. Sans mécanisme approprié, cela peut conduire
à plusieurs problèmes :

2.1.1 Lectures sales (Dirty Reads)


Une transaction lit des données qui ont été modifiées par une autre transaction non
validée.

Problème
La transaction A modifie une donnée. La transaction B lit cette donnée modifiée.
La transaction A est annulée (rollback). La transaction B travaille maintenant avec
une donnée qui n’existe pas officiellement.

6
2.1.2 Lectures non reproductibles (Non-repeatable Reads)
Une transaction relit une donnée qu’elle a déjà lue et constate qu’elle a été modifiée
par une autre transaction.

Problème
La transaction A lit une donnée. La transaction B modifie cette donnée et valide.
La transaction A relit la même donnée et obtient une valeur différente.

2.1.3 Lectures fantômes (Phantom Reads)


Une transaction exécute une requête qui renvoie un ensemble de lignes. Une autre
transaction insère de nouvelles lignes qui correspondent aux critères de la requête de la
première transaction.

Problème
La transaction A exécute une requête qui renvoie un ensemble de lignes. La tran-
saction B insère de nouvelles lignes qui correspondent aux critères de la requête de
A. Si A réexécute sa requête, elle verra des lignes "fantômes" qui n’existaient pas
lors de sa première exécution.

2.2 Imbrication des transactions


Dans les applications complexes, les méthodes qui utilisent des transactions peuvent
s’appeler entre elles. Comment gérer ces situations ? Faut-il créer une nouvelle transaction
ou utiliser celle existante ?

Problème
La méthode A démarre une transaction et appelle la méthode B. La méthode B
a également besoin d’une transaction. Devrait-elle créer sa propre transaction ou
participer à celle de A ? Que se passe-t-il si B échoue mais que A réussit ?

3 Solutions avec Spring : Niveaux d’isolation


Spring Framework fournit une abstraction élégante pour gérer les transactions, en
s’appuyant sur les capacités des systèmes sous-jacents (bases de données, JTA, etc.). Les
niveaux d’isolation permettent de contrôler comment les transactions interagissent entre
elles.

3.1 DEFAULT
Problématique
Comment choisir un niveau d’isolation approprié sans connaître les spécificités de
chaque base de données ou système transactionnel ?

7
Solution
Le niveau DEFAULT utilise le niveau d’isolation par défaut du système sous-jacent.
Cela permet de s’adapter automatiquement aux paramètres optimaux définis par
les administrateurs de la base de données.

Ce niveau utilise le niveau d’isolation par défaut du système sous-jacent. C’est géné-
ralement un bon choix lorsque vous n’avez pas de besoins spécifiques.
1 @Transactional ( isolation = Isolation . DEFAULT )
2 public void maMethode () {
3 // Code transactionnel
4 }

3.2 READ_UNCOMMITTED
Problématique
Comment maximiser les performances dans des scénarios où la cohérence absolue
des données n’est pas critique et où la rapidité d’accès est prioritaire ?

Solution
READ_UNCOMMITTED offre les meilleures performances en permettant la lec-
ture de données non validées. C’est utile pour les rapports en temps réel ou les
tableaux de bord où des données approximatives sont acceptables et où la rapidité
est essentielle.

C’est le niveau le plus bas d’isolation. Une transaction peut lire des données modifiées
par d’autres transactions non validées.

Attention
Ce niveau permet les lectures sales, les lectures non reproductibles et les lectures
fantômes. À utiliser avec précaution !

1 @Transactional ( isolation = Isolation . READ_UNCOMMITTED )


2 public void maMethode () {
3 // Code transactionnel avec faible isolation
4 }

3.3 READ_COMMITTED
Problématique

Comment éviter de lire des données potentiellement invalides (qui pourraient être
annulées) tout en maintenant de bonnes performances ?

8
Solution
READ_COMMITTED assure que seules les données validées sont lues, éliminant
ainsi le problème des lectures sales. C’est un bon compromis entre performance et
cohérence pour la plupart des applications.

Une transaction ne peut lire que les données validées par d’autres transactions. Cela
évite les lectures sales mais permet toujours les lectures non reproductibles et les lectures
fantômes.
1 @Transactional ( isolation = Isolation . READ_COMMITTED )
2 public void maMethode () {
3 // Code transactionnel
4 }

3.4 REPEATABLE_READ
Problématique
Comment garantir qu’une transaction obtiendra toujours les mêmes résultats lors-
qu’elle relit les mêmes données, même si d’autres transactions modifient ces données
entre-temps ?

Solution
REPEATABLE_READ utilise des verrous ou des versions de données pour garantir
que les données lues par une transaction restent cohérentes tout au long de son
exécution. C’est essentiel pour les rapports financiers ou les calculs qui nécessitent
une vue cohérente des données.

Ce niveau garantit que si une transaction lit une donnée, elle obtiendra la même valeur
si elle la relit, même si d’autres transactions ont modifié cette donnée entre-temps. Cela
évite les lectures sales et les lectures non reproductibles, mais permet toujours les lectures
fantômes.
1 @Transactional ( isolation = Isolation . REPEATABLE_READ )
2 public void maMethode () {
3 // Code transactionnel avec isolation moyenne
4 }

3.5 SERIALIZABLE
Problématique
Comment garantir une cohérence totale des données dans des situations critiques
où aucune anomalie n’est acceptable, même au prix de performances réduites ?

9
Solution
SERIALIZABLE offre le plus haut niveau d’isolation en exécutant les transactions
comme si elles étaient séquentielles. C’est crucial pour les opérations financières
sensibles, les mises à jour de soldes ou toute opération où l’intégrité des données
est primordiale.

C’est le niveau le plus élevé d’isolation. Les transactions s’exécutent comme si elles
étaient sérialisées, c’est-à-dire exécutées l’une après l’autre. Cela évite tous les problèmes
de concurrence mais peut considérablement réduire les performances.
1 @Transactional ( isolation = Isolation . SERIALIZABLE )
2 public void maMethode () {
3 // Code transactionnel avec isolation maximale
4 }

Tableau récapitulatif des niveaux d’isolation


Niveau Lectures Lectures non Lectures Performance
d’isolation sales reproductibles fantômes
READ_UNCOMMITTED Oui Oui Oui Très haute
READ_COMMITTED Non Oui Oui Haute
REPEATABLE_READ Non Non Oui Moyenne
SERIALIZABLE Non Non Non Basse

4 Solutions avec Spring : Propagation des transactions


La propagation définit comment les transactions se comportent lorsqu’une méthode
transactionnelle en appelle une autre.

4.1 REQUIRED (Par défaut)


Problématique
Comment garantir qu’un ensemble d’opérations s’exécute dans une transaction,
qu’une transaction existe déjà ou non ?

Solution
REQUIRED assure qu’une transaction est toujours disponible. Si une transaction
existe déjà, la méthode s’y joint ; sinon, une nouvelle transaction est créée. C’est
idéal pour les opérations qui doivent être atomiques mais peuvent faire partie d’une
unité de travail plus large.

Si une transaction existe déjà, la méthode s’exécute dans cette transaction. Sinon, une
nouvelle transaction est créée.

10
Cas d’utilisation
C’est le comportement par défaut et convient à la plupart des cas d’utilisation.
Utilisez-le lorsque votre méthode doit s’exécuter dans une transaction et que vous
voulez participer à une transaction existante si elle existe.

1 @Transactional ( propagation = Propagation . REQUIRED )


2 public void maMethode () {
3 // Code transactionnel
4 autreService . autreMethode () ; // Utilise la m m e transaction
5 }

4.2 SUPPORTS
Problématique
Comment créer des méthodes flexibles qui peuvent fonctionner avec ou sans tran-
saction selon le contexte d’appel ?

Solution
SUPPORTS permet à une méthode de s’adapter au contexte transactionnel exis-
tant. Elle s’exécutera dans une transaction si elle est appelée dans un contexte
transactionnel, ou sans transaction si ce n’est pas le cas. C’est utile pour les opéra-
tions de lecture qui peuvent bénéficier d’une transaction mais n’en nécessitent pas
absolument.

La méthode s’exécute dans la transaction existante si elle existe. Sinon, elle s’exécute
sans transaction.

Cas d’utilisation
Utilisez ce mode lorsque votre méthode peut fonctionner avec ou sans transaction.
Par exemple, pour des opérations de lecture qui peuvent bénéficier d’une transaction
mais n’en nécessitent pas absolument.

1 @Transactional ( propagation = Propagation . SUPPORTS )


2 public List < Produit > listerProduits () {
3 // Cette m t h o d e s ’ e x c u t e r a dans une transaction si elle existe ,
4 // sinon elle s ’ e x c u t e r a sans transaction
5 return prod uitRepos itory . findAll () ;
6 }

4.3 MANDATORY
Problématique
Comment s’assurer qu’une méthode n’est jamais appelée hors d’un contexte tran-
sactionnel, pour éviter des modifications incohérentes ?

11
Solution
MANDATORY force l’existence préalable d’une transaction. Si aucune transaction
n’est active lors de l’appel, une exception est levée. C’est un mécanisme de sécurité
pour garantir que certaines opérations critiques ne sont exécutées que dans le cadre
d’une transaction plus large gérée par l’appelant.

La méthode doit s’exécuter dans une transaction existante. Si aucune transaction


n’existe, une exception est levée.

Cas d’utilisation
Utilisez ce mode lorsque votre méthode doit obligatoirement s’exécuter dans le
contexte d’une transaction existante. Cela permet de s’assurer que le code appelant
a déjà démarré une transaction.

1 @Transactional ( propagation = Propagation . MANDATORY )


2 public void mi seAJourC ritique ( Long id , String valeur ) {
3 // Cette m t h o d e exige qu ’ une transaction soit d j en cours
4 // Sinon , une exception T r a n s a c t i o n R e q u i r e d E x c e p t i o n sera l e v e
5 entite . setValeur ( valeur ) ;
6 repository . save ( entite ) ;
7 }

4.4 REQUIRES_NEW
Problématique
Comment isoler certaines opérations pour qu’elles soient indépendantes de la tran-
saction appelante, que celle-ci réussisse ou échoue ?

Solution
REQUIRES_NEW crée systématiquement une nouvelle transaction indépendante,
suspendant temporairement la transaction existante. C’est essentiel pour les opé-
rations qui doivent être validées indépendamment, comme la journalisation d’évé-
nements ou les notifications qui doivent persister même si l’opération principale
échoue.

La méthode s’exécute toujours dans une nouvelle transaction. Si une transaction existe
déjà, elle est suspendue jusqu’à ce que la nouvelle transaction soit terminée.

Cas d’utilisation
Utilisez ce mode lorsque vous voulez que votre opération soit complètement indé-
pendante de la transaction appelante. Par exemple, pour la journalisation ou les
audits qui doivent être persistés même si la transaction principale échoue.

1 @Transactional ( propagation = Propagation . REQUIRES_NEW )


2 public void jo urnalise rAction ( String action ) {
3 // Cette m t h o d e s ’ e x c u t e toujours dans une nouvelle transaction
4 Journal journal = new Journal () ;

12
5 journal . setAction ( action ) ;
6 journal . setDate ( new Date () ) ;
7 journalRe pository . save ( journal ) ;
8 }

4.5 NOT_SUPPORTED
Problématique
Comment exécuter certaines opérations en dehors de toute transaction, même si
elles sont appelées depuis un contexte transactionnel ?

Solution
NOT_SUPPORTED suspend toute transaction existante pendant l’exécution de la
méthode. C’est utile pour les opérations qui ne nécessitent pas de transaction ou qui
pourraient être affectées négativement par une transaction (comme des requêtes de
lecture intensives ou des opérations sur des systèmes externes non transactionnels).

La méthode s’exécute toujours sans transaction. Si une transaction existe, elle est
suspendue pendant l’exécution de la méthode.

Cas d’utilisation
Utilisez ce mode pour les opérations qui ne doivent pas être transactionnelles et
qui pourraient être affectées négativement par une transaction (par exemple, des
opérations de lecture intensives).

1 @Transactional ( propagation = Propagation . NOT_SUPPORTED )


2 public List < Statistique > c a lc u l e r S t a t i s t i q u e s () {
3 // Cette m t h o d e s ’ e x c u t e toujours sans transaction
4 // Utile pour des o p r a t i o n s de lecture lourdes
5 return s t a t i s t i q u e R e p o s i t o r y . c a l c u l e r S t a t i s t i q u e s C o m p l e x e s () ;
6 }

4.6 NEVER
Problématique
Comment empêcher formellement qu’une méthode soit appelée dans un contexte
transactionnel ?

Solution
NEVER garantit qu’aucune transaction n’est active lors de l’appel de la méthode. Si
une transaction est détectée, une exception est levée. C’est un mécanisme de sécurité
pour les opérations qui ne doivent absolument pas être exécutées dans un contexte
transactionnel, par exemple pour éviter des verrous prolongés ou des interactions
avec des systèmes externes incompatibles.

13
La méthode s’exécute sans transaction. Si une transaction existe, une exception est
levée.

Cas d’utilisation
Utilisez ce mode pour les méthodes qui ne doivent jamais être appelées dans un
contexte transactionnel, par exemple pour des raisons de performance ou de com-
patibilité.

1 @Transactional ( propagation = Propagation . NEVER )


2 public void o p e r a t i o n N o n T r a n s a c t i o n n e l l e () {
3 // Cette m t h o d e l v e r a une exception si elle est a p p e l e
4 // dans un contexte transactionnel
5 }

4.7 NESTED
Problématique
Comment créer des points de sauvegarde dans une transaction, permettant d’annu-
ler partiellement certaines opérations sans affecter toute la transaction ?

Solution
NESTED crée une transaction imbriquée avec un point de sauvegarde (savepoint).
Si la transaction imbriquée échoue, seules les opérations effectuées dans cette sous-
transaction sont annulées, tandis que la transaction parente peut continuer. C’est
utile pour les opérations composées de sous-tâches qui peuvent échouer individuel-
lement sans compromettre l’ensemble.

La méthode s’exécute dans une transaction imbriquée si une transaction existe déjà.
Sinon, elle se comporte comme REQUIRED.

Cas d’utilisation
Utilisez ce mode lorsque vous voulez pouvoir annuler une partie de la transaction
sans affecter la transaction parente. Notez que tous les gestionnaires de transactions
ne prennent pas en charge les transactions imbriquées.

1 @Transactional ( propagation = Propagation . NESTED )


2 public void sousOperation () {
3 // Cette m t h o d e s ’ e x c u t e dans une transaction i m b r i q u e
4 // Si elle choue , seule cette partie sera a n n u l e
5 }

Attention
Le support des transactions imbriquées dépend du gestionnaire de transactions sous-
jacent. Par exemple, JTA ne supporte pas les transactions imbriquées, tandis que
JDBC avec savepoints les supporte.

14
5 Gestion des exceptions et rollback
5.1 Mécanisme de rollback dans Spring
Spring offre un mécanisme sophistiqué pour gérer les exceptions et déclencher des
rollbacks automatiques lorsque nécessaire.

Comportement par défaut


Par défaut, Spring effectue un rollback automatique lorsqu’une exception non véri-
fiée (runtime exception) est levée dans une méthode transactionnelle. Les exceptions
vérifiées (checked exceptions) ne déclenchent pas de rollback automatique.

1 @Transactional
2 public void m e t h o d e A v e c R o l l b a c k A u t o m a t i q u e () {
3 // Si une RuntimeException est l e v e ici , Spring effectuera un
rollback
4 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Cette exception d c l e n c h e r a un
rollback " ) ;
5
6 // Si une exception v r i f i e est l e v e ici , Spring ne fera PAS de
rollback par d f a u t
7 throw new IOException ( " Cette exception ne d c l e n c h e r a pas de
rollback " ) ;
8 }

5.2 Personnalisation du comportement de rollback


Spring permet de personnaliser quelles exceptions doivent déclencher un rollback et
lesquelles ne le doivent pas.

Problématique
Comment contrôler précisément quelles exceptions doivent déclencher un rollback
et lesquelles doivent être ignorées ?

Solution
Spring permet de spécifier explicitement les types d’exceptions qui doivent déclen-
cher un rollback (rollbackFor) et ceux qui ne doivent pas en déclencher (noRoll-
backFor). Cela offre un contrôle fin sur le comportement transactionnel en cas d’ex-
ception.

1 @Transactional (
2 rollbackFor = { Exception . class } , // Toutes les exceptions
d c l e n c h e r o n t un rollback
3 noRollbackFor = { NotFound Exceptio n . class } // Sauf celle - ci
4 )
5 public void m e t h o d e A v e c R o l l b a c k P e r s o n n a l i s e () throws Exception {
6 // Le code ici
7 }

15
5.3 Rollback programmatique
Dans certains cas, vous pourriez avoir besoin de déclencher un rollback manuellement,
en fonction de conditions métier spécifiques.

Problématique
Comment déclencher un rollback sans lever d’exception, par exemple lorsqu’une
condition métier n’est pas satisfaite ?

Solution
Spring permet de marquer la transaction courante comme "rollback-only", ce qui
garantit qu’elle sera annulée à la fin, même si aucune exception n’est levée. C’est
utile pour les cas où la logique métier détecte une condition d’erreur mais ne souhaite
pas interrompre l’exécution avec une exception.

1 @Service
2 public class MonService {
3
4 @Autowired
5 private T r a n s a c t i o n A s p e c t S u p p o r t t r a n s a c t i o n A s p e c t S u p p o r t ;
6
7 @Transactional
8 public void m e t h o d e A v e c R o l l b a c k P r o g r a m m a t i q u e ( boolean condition ) {
9 // Logique m t i e r
10

11 if (! condition ) {
12 // Marquer la transaction pour rollback sans lever d ’
exception
13 T r a n s a c t i o n A s p e c t S u p p o r t . c u r r e n t T r a n s a c t i o n S t a t u s () .
setRollbackOnly () ;
14 return ; // Continuer l ’ e x c u t i o n mais la transaction sera
annul e
15 }
16
17 // Suite du code
18 }
19 }

5.4 Exceptions de rollback et transactions imbriquées


La gestion des exceptions devient plus complexe avec les transactions imbriquées ou
les différents modes de propagation.

Problématique
Comment les exceptions affectent-elles les transactions imbriquées ou les transac-
tions avec différentes stratégies de propagation ?

16
Solution
Le comportement dépend de la stratégie de propagation :
— Avec REQUIRED, une exception dans une méthode interne provoque un
rollback de toute la transaction
— Avec REQUIRES_NEW, seule la transaction interne subit un rollback, la
transaction externe continue
— Avec NESTED, seule la partie imbriquée est annulée, la transaction parente
peut continuer

1 @Service
2 public class ServiceExterne {
3
4 @Autowired
5 private ServiceInterne serviceInterne ;
6
7 @Transactional
8 public void methodeExterne () {
9 // Code avant
10
11 try {
12 serviceInterne . methodeInterne () ; // Peut lever une exception
13 } catch ( Exception e ) {
14 // Avec REQUIRES_NEW , la transaction interne est d j
annul e
15 // mais la transaction externe peut continuer
16
17 // Avec REQUIRED , cette gestion d ’ exception ne suffit pas
18 // car la transaction est d j m a r q u e pour rollback
19 }
20
21 // Code a p r s - s ’ e x c u t e r a uniquement si la m t h o d e interne
22 // utilise REQUIRES_NEW ou NESTED
23 }
24 }
25

26 @Service
27 public class ServiceInterne {
28
29 @Transactional ( propagation = Propagation . REQUIRES_NEW )
30 public void methodeInterne () {
31 // Si une exception est l e v e ici , seule cette transaction est
annul e
32 throw new RuntimeException ( " Erreur " ) ;
33 }
34 }

6 Mise en œuvre pratique


6.1 Configuration des transactions dans Spring
Spring offre plusieurs façons de configurer les transactions :

17
6.1.1 Configuration par annotations
C’est l’approche la plus simple et la plus courante.
1 @Configuration
2 @EnableTransactionManagement
3 public class AppConfig {
4
5 @Bean
6 public DataSource dataSource () {
7 // Configuration de la source de d o n n e s
8 }
9
10 @Bean
11 public P l a t f o r m T r a n s a c t i o n M a n a g e r tr an sa cti on Ma nag er () {
12 return new D a t a S o u r c e T r a n s a c t i o n M a n a g e r ( dataSource () ) ;
13 }
14 }

Ensuite, vous pouvez utiliser l’annotation @Transactional sur vos méthodes ou classes :
1 @Service
2 public class MonService {
3
4 @Transactional ( isolation = Isolation . READ_COMMITTED ,
5 propagation = Propagation . REQUIRED ,
6 timeout = 30 ,
7 readOnly = false ,
8 rollbackFor = Exception . class )
9 public void m a M e t h o d e T r a n s a c t i o n n e l l e () {
10 // Code m t i e r
11 }
12 }

6.1.2 Configuration programmatique


Pour les cas plus complexes, vous pouvez gérer les transactions programmatiquement :
1 @Service
2 public class MonService {
3
4 private final P l a t f o r m T r a n s a c t i o n M a n a g e r tr ans ac ti onM an ag er ;
5
6 public MonService ( P l a t f o r m T r a n s a c t i o n M a n a g e r tra ns ac tio nM an age r ) {
7 this . tr an sa cti on Ma nag er = tr an sac ti on Man ag er ;
8 }
9
10 public void m a M e t h o d e T r a n s a c t i o n n e l l e () {
11 Tr a n s a c t i o n D e f i n i t i o n def = new D e f a u l t T r a n s a c t i o n D e f i n i t i o n () ;
12 Trans actionSta tus status = tr ans ac ti onM an ag er . getTransaction ( def
);
13
14 try {
15 // Code m t i e r
16 tra ns ac tio nM an ag er . commit ( status ) ;
17 } catch ( Exception e ) {
18 tra ns ac tio nM an ag er . rollback ( status ) ;
19 throw e ;
20 }

18
21 }
22 }

6.2 Exemple complet : Système de transfert bancaire


Voici un exemple complet d’un système de transfert bancaire utilisant les transactions
Spring :
1 @Entity
2 public class Compte {
3 @Id
4 private Long id ;
5 private String numero ;
6 private BigDecimal solde ;
7

8 // Getters et setters
9 }
10
11 @Repository
12 public interface CompteRepository extends JpaRepository < Compte , Long > {
13 Compte findByNumero ( String numero ) ;
14 }
15
16 @Service
17 public class TransfertService {
18
19 private final CompteRepository compteRepository ;
20 private final JournalService journalService ;
21
22 public TransfertService ( CompteRepository compteRepository ,
23 JournalService journalService ) {
24 this . compteRepository = compteRepository ;
25 this . journalService = journalService ;
26 }
27
28 @Transactional ( isolation = Isolation . REPEATABLE_READ )
29 public void transferer ( String sourceNumero , String destinationNumero
,
30 BigDecimal montant ) {
31
32 // V r i f i c a t i o n du montant
33 if ( montant . compareTo ( BigDecimal . ZERO ) <= 0) {
34 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Le montant doit tre
positif " ) ;
35 }
36
37 // R c u p r a t i o n des comptes
38 Compte source = compteRepository . findByNumero ( sourceNumero ) ;
39 Compte destination = compteRepository . findByNumero (
dest inationNu mero ) ;
40
41 if ( source == null || destination == null ) {
42 throw new I l l e g a l A r g u m e n t E x c e p t i o n ( " Compte introuvable " ) ;
43 }
44
45 // V r i f i c a t i o n du solde
46 if ( source . getSolde () . compareTo ( montant ) < 0) {

19
47 throw new I l l e g a l S t a t e E x c e p t i o n ( " Solde insuffisant " ) ;
48 }
49
50 // R a l i s a t i o n du transfert
51 source . setSolde ( source . getSolde () . subtract ( montant ) ) ;
52 destination . setSolde ( destination . getSolde () . add ( montant ) ) ;
53
54 // Sauvegarde des modifications
55 compteRepository . save ( source ) ;
56 compteRepository . save ( destination ) ;
57
58 // Journalisation du transfert ( dans une nouvelle transaction )
59 journalService . j o u r n a l i s e r T ra n s f e r t ( sourceNumero ,
destinationNumero , montant ) ;
60 }
61 }
62
63 @Service
64 public class JournalService {
65
66 private final Journal Repositor y journal Reposito ry ;
67
68 public JournalService ( J ournalRep ository j ournalRe pository ) {
69 this . journal Reposito ry = journalR epositor y ;
70 }
71
72 @Transactional ( propagation = Propagation . REQUIRES_NEW )
73 public void j ou r n a l i s e r T r a n s f e r t ( String source , String destination ,
74 BigDecimal montant ) {
75 Journal journal = new Journal () ;
76 journal . setTypeOperation ( " TRANSFERT " ) ;
77 journal . setCompteSource ( source ) ;
78 journal . s e t C o m p t e D e s t i n a t i o n ( destination ) ;
79 journal . setMontant ( montant ) ;
80 journal . setDate ( new Date () ) ;
81

82 journ alReposit ory . save ( journal ) ;


83
84 // M m e si la transaction principale choue a p r s ce point ,
85 // cette e n t r e de journal sera p e r s i s t e g r c e
REQUIRES_NEW
86 }
87 }

7 Bonnes pratiques
7.1 Choisir le bon niveau d’isolation
— Commencez avec le niveau par défaut de votre base de données (généralement
READ_COMMITTED).
— N’utilisez READ_UNCOMMITTED que si vous êtes absolument certain que les
lectures sales ne poseront pas de problème.
— Utilisez REPEATABLE_READ lorsque vous avez besoin de cohérence dans les
lectures multiples.

20
— Réservez SERIALIZABLE pour les opérations critiques où la cohérence est plus
importante que la performance.

7.2 Choisir la bonne stratégie de propagation


— REQUIRED est adapté à la plupart des cas d’utilisation.
— REQUIRES_NEW est utile pour les opérations qui doivent être indépendantes.
— NESTED permet une granularité plus fine dans la gestion des erreurs.
— Évitez d’utiliser SUPPORTS sauf si vous avez une bonne raison de le faire.

7.3 Optimiser les performances


— Utilisez l’attribut readOnly=true pour les transactions en lecture seule.
— Minimisez la durée des transactions.
— Évitez les transactions imbriquées profondes.
— Utilisez le niveau d’isolation le plus bas qui répond à vos besoins.

7.4 Gérer les exceptions


Spring distingue deux types d’exceptions :
— Exceptions non vérifiées (runtime) : Par défaut, elles provoquent un rollback.
— Exceptions vérifiées : Par défaut, elles ne provoquent pas de rollback.
Vous pouvez personnaliser ce comportement avec les attributs rollbackFor et noRoll-
backFor :
1 @Transactional ( rollbackFor = { Exception . class } ,
2 noRollbackFor = { F i l e N o t F o u n d E x c e p t i o n . class })
3 public void maMethode () {
4 // Code transactionnel
5 }

8 Cas d’utilisation avancés


8.1 Transactions distribuées
Pour les applications qui interagissent avec plusieurs sources de données, vous pouvez
utiliser JTA (Java Transaction API) :
1 @Configuration
2 @EnableTransactionManagement
3 public class JtaConfig {
4
5 @Bean
6 public J t a T r a n s a c t i o n M a n a g e r t ra nsa ct io nMa na ge r () {
7 return new J t a T r a n s a c t i o n M a n a g e r () ;
8 }
9 }

21
8.2 Transactions programmatiques avec TransactionTemplate
Pour les cas où les annotations ne sont pas suffisantes :
1 @Service
2 public class MonService {
3
4 private final T r an s a ct i on T em p l at e t r an s a ct i on T em p l at e ;
5
6 public MonService ( P l a t f o r m T r a n s a c t i o n M a n a g e r tra ns ac tio nM an age r ) {
7 this . t r an s a ct i on T em p l at e = new T ra n sa c ti o n Te m pl a t e (
tr an sa cti on Ma nag er ) ;
8 this . t r an s a ct i on T em p l at e . setIso lationLe vel (
9 TransactionDefinition . ISOLATION_READ_COMMITTED );
10 this . t r an s ac t i on T em p l at e . s e t P r o p a g a t i o n B e h a v i o r (
11 T r a n s a c t i o n D e f i n i t i o n . P R O P A G A T I O N _ R E Q UI R E D ) ;
12 }
13

14 public void maMethode () {


15 t r a ns a ct i o nT e mp l at e . execute ( status -> {
16 // Code transactionnel
17 return null ;
18 }) ;
19 }
20 }

8.3 Tests avec transactions


Spring fournit des outils pour tester les transactions :
1 @SpringBootTest
2 @Transactional
3 public class MonServiceTest {
4
5 @Autowired
6 private MonService service ;
7
8 @Test
9 public void testMethode () {
10 // Le test s ’ e x c u t e dans une transaction qui sera
11 // automatiquement a n n u l e la fin
12 service . maMethode () ;
13 // Assertions
14 }
15
16 @Test
17 @Commit
18 public void t e s t M e t h o d e A v e c C o m m i t () {
19 // Ce test validera la transaction la fin
20 service . maMethode () ;
21 // Assertions
22 }
23 }

22
9 Conclusion
La gestion des transactions est un aspect fondamental du développement d’applica-
tions robustes. Spring offre une abstraction puissante qui simplifie considérablement cette
tâche. En comprenant les niveaux d’isolation et les stratégies de propagation, vous pouvez
concevoir des applications qui garantissent l’intégrité des données tout en maintenant de
bonnes performances.
Les points clés à retenir sont :
— Les transactions garantissent les propriétés ACID.
— Les transactions sont associées au thread d’exécution courant dans Spring.
— Les niveaux d’isolation contrôlent comment les transactions interagissent entre
elles.
— Les stratégies de propagation définissent comment les transactions se comportent
lorsqu’une méthode transactionnelle en appelle une autre.
— Spring offre un contrôle fin sur le comportement de rollback en cas d’exception.
— Choisissez toujours le niveau d’isolation et la stratégie de propagation en fonction
de vos besoins spécifiques.

10 Exercices pratiques
1. Implémentez un système de réservation où plusieurs utilisateurs peuvent réserver
le même produit. Utilisez le niveau d’isolation approprié pour éviter les problèmes
de concurrence.
2. Créez un service qui effectue plusieurs opérations, certaines devant être dans la
même transaction et d’autres dans des transactions séparées.
3. Implémentez un mécanisme de compensation pour annuler les effets d’une transac-
tion déjà validée en cas d’erreur dans une étape ultérieure.

11 Références
— Documentation officielle de Spring : [Link]
docs/current/reference/html/[Link]#transaction
— Martin Fowler, "Patterns of Enterprise Application Architecture"
— Pramod J. Sadalage et Martin Fowler, "NoSQL Distilled"
— Spring in Action, Craig Walls

23

Vous aimerez peut-être aussi