Accueil Spring @Transactional
SPRING

Spring @Transactional :
les pièges qui provoquent de vrais bugs en production

5 cas concrets, des exemples de code et des explications simples pour éviter les erreurs les plus courantes.

8 min de lecture 5 cas pratiques ▱ À garder sous la main
Contexte

Une commande et son audit doivent être enregistrés dans la même transaction. process() est appelée depuis un autre bean Spring.

java
@Service 
public class OrderService { 

    public void process(Order order) { 
        saveOrder(order); // appel interne 
    } 

    @Transactional 
    public void saveOrder(Order order) { 
        orderRepository.save(order);
        auditRepository.save(
            new Audit(order.getId())
        );
    } 
}

? Que se passe-t-il avec le @Transactional de saveOrder() ?

Explication

Dans le mode proxy standard de Spring, @Transactional est appliqué lorsqu’un appel traverse le proxy Spring. Ici, process() appelle directement saveOrder() sur la même instance. L’appel ne repasse donc pas par le proxy et la transaction déclarée sur saveOrder() n’est pas créée.

!
Pourquoi c’est dangereux ?

Le développeur peut croire que l’enregistrement de la commande et de l’audit forme une seule opération atomique. En réalité, cette transaction englobante n’existe pas dans ce chemin d’appel. Les repositories Spring Data peuvent exécuter leurs propres transactions : une première sauvegarde peut donc réussir alors qu’une opération suivante échoue.

💡
À retenir

Un @Transactional placé sur une méthode ne suffit pas : l’appel doit passer par le proxy Spring. Une solution courante consiste à déplacer la méthode transactionnelle dans un autre bean Spring afin que l’appel soit intercepté.

Contexte

Par défaut, Spring ne déclenche pas le rollback de la même manière pour toutes les exceptions.

java
@Transactional
public void process() throws Exception {
    repository.save(order);
    throw new Exception("Payment failed");
}

? Que se passe-t-il par défaut ?

Explication

Par défaut, Spring déclenche automatiquement un rollback pour les RuntimeException et les Error. Une exception checked comme Exception ne déclenche pas automatiquement ce rollback.

!
Pourquoi c’est dangereux ?

Une erreur métier peut remonter alors que les modifications en base ont tout de même été validées.

💡
À retenir

Utilise par exemple @Transactional(rollbackFor = Exception.class) lorsque le comportement attendu l’exige.

Contexte

Une méthode privée porte directement @Transactional.

java
@Service
public class UserService {

    @Transactional
    private void saveUser() {
        repository.save(user);
    }
}

? La méthode privée sera-t-elle interceptée ?

Explication

Une méthode privée ne peut pas être interceptée par le mécanisme de proxy Spring. Avec un proxy basé sur sous-classe, une méthode final pose également problème puisqu’elle ne peut pas être redéfinie.

💡
À retenir

Pour les transactions déclaratives classiques, préfère des méthodes interceptables appelées à travers le proxy.

Contexte

Une méthode transactionnelle appelle une autre méthode avec Propagation.REQUIRES_NEW.

java
@Transactional
public void outer() {
    updateOrder();
    auditService.audit();
}

@Transactional(
    propagation = Propagation.REQUIRES_NEW
)
public void audit() {
    auditRepository.save(log);
}

? Quel comportement est attendu ?

Explication

REQUIRES_NEW démarre une nouvelle transaction physique indépendante. La transaction existante est suspendue pendant son exécution.

!
Pourquoi c’est important ?

La transaction interne peut être commitée même si la transaction externe échoue ensuite.

💡
À retenir

C’est utile pour certains audits ou traitements isolés, mais il faut comprendre les conséquences métier et l’utilisation supplémentaire de connexions.

Contexte

Un email est envoyé avant la fin de la transaction. Une erreur survient ensuite.

java
@Transactional
public void register() {

    userRepository.save(user);

    emailService.sendWelcomeEmail(user);

    if (error) {
        throw new RuntimeException("fail");
    }
}

? Quel est le problème principal ?

Explication

La transaction de base de données peut être rollbackée, mais un email déjà envoyé ou un appel externe déjà effectué ne sera pas automatiquement annulé.

!
Pourquoi c’est dangereux ?

L’utilisateur peut recevoir une confirmation alors que l’opération correspondante n’existe finalement pas en base.

💡
À retenir

Pour les effets externes critiques, pense notamment aux événements après commit ou à un pattern Outbox lorsque la garantie de livraison doit être robuste.

🏆

Tu as terminé les 5 pièges !

Tu sais maintenant éviter plusieurs erreurs classiques avec @Transactional.

Application DevUpNow
✓ 240+ contenus techniques ✓ Java, Spring, JPA et PostgreSQL ✓ Mode entretien ✓ Corrections détaillées ✓ Progression personnalisée
Continuer sur DevUpNow
DEVUPNOW

Continue à te challenger

240+ questions Java, Spring, JPA et PostgreSQL avec corrections détaillées et mode entretien.