lundi 30 octobre 2023

[Spring ] La gestion des transactions avec Spring @Transactional Rollback

 Introduction 

Lorsque vous travaillez avec des applications Spring, la gestion des transactions est essentielle pour garantir la cohérence et la fiabilité des opérations de base de données. L’une des questions clés dans ce contexte est de savoir comment gérer les exceptions qui se produisent pendant une transaction. Par défaut, Spring définit une règle de base pour déterminer si une transaction doit être annulée en cas d’exception. 

Cependant, il peut être nécessaire de personnaliser ces règles en fonction de vos besoins spécifiques. C’est là qu’intervient l’attribut “rollbackFor”.

Le Problème

Imaginons que vous travaillez sur une application de commerce électronique qui utilise Spring pour gérer les transactions. Lorsqu’un client passe une commande, plusieurs opérations de base de données doivent être effectuées, comme la mise à jour de l’inventaire, la création d’un nouvel enregistrement de commande et la déduction du solde du client. Si une exception se produit lors de l’une de ces opérations (par exemple, si l’inventaire est insuffisant), vous voulez que toutes les opérations soient annulées pour maintenir la cohérence des données. Cependant, par défaut, Spring ne fait un rollback que pour les exceptions non vérifiées (Unchecked Exception), ce qui peut ne pas être suffisant pour votre cas d’utilisation.

La Solution

Pour résoudre ce problème, vous pouvez utiliser l’attribut “rollbackFor” de l’annotation @Transactional de Spring. Cet attribut vous permet de spécifier les exceptions pour lesquelles un rollback doit être effectué. Par exemple, si vous voulez faire un rollback pour une exception vérifiée (Checked Exception) spécifique (disons InsufficientInventoryException), vous pouvez le faire comme suit :

@Transactional(rollbackFor = InsufficientInventoryException.class)
public void placeOrder(Order order) throws InsufficientInventoryException {
    // Code pour passer la commande
}

Dans cet exemple, si une InsufficientInventoryException est levée lors de l’exécution de la méthode placeOrder(), Spring fera un rollback de toutes les opérations de base de données effectuées dans cette méthode.

Pour tester cette fonctionnalité, vous pouvez utiliser JUnit et Mockito. Par exemple :

@Test
public void whenInsufficientInventory_thenRollback() {
    doThrow(InsufficientInventoryException.class).when(inventoryService).deductInventory(any(Order.class));
    
    assertThrows(InsufficientInventoryException.class, () -> {
        orderService.placeOrder(new Order());
    });
    
    verify(inventoryService, never()).deductInventory(any(Order.class));
}

Dans ce test, nous simulons une InsufficientInventoryException lors de l’appel à inventoryService.deductInventory(). Ensuite, nous vérifions que lorsque cette exception est levée, aucune déduction d’inventaire n’est effectuée.

Discussion

L’utilisation de l’attribut “rollbackFor” offre une grande flexibilité pour gérer les transactions dans Spring. Cependant, il est important de noter que cette approche peut conduire à une logique de gestion des transactions complexe si elle est utilisée sans discernement. Il est recommandé d’utiliser “rollbackFor” uniquement lorsque le comportement par défaut de Spring ne suffit pas.

D’autre part, il convient également de noter que “rollbackFor” ne fonctionne que lorsque la méthode annotée est appelée à partir d’un autre bean Spring. Si elle est appelée à partir du même bean (c’est-à-dire en appelant une autre méthode publique de la même classe), l’annotation @Transactional (et donc “rollbackFor”) sera ignorée.

Un autre concept clé est la propagation des transactions. La propagation détermine comment les transactions sont gérées lorsque vous avez plusieurs méthodes @Transactional imbriquées. Par exemple, si vous avez une méthode A qui appelle une méthode B, et que les deux sont annotées avec @Transactional, comment les transactions sont-elles gérées ? C’est là qu’intervient la propagation.

Par défaut, la propagation est définie sur Propagation.REQUIRED, ce qui signifie que si une transaction est déjà en cours lorsqu’une méthode @Transactional est appelée, cette transaction existante sera utilisée au lieu d’en créer une nouvelle. Cependant, vous pouvez modifier ce comportement en utilisant l’attribut propagation de @Transactional. Par exemple, si vous définissez propagation = Propagation.REQUIRES_NEW, une nouvelle transaction sera toujours créée, même s’il en existe déjà une.

Enfin et surtout, il convient également d’évoquer le rôle des proxies dans la gestion des transactions avec Spring. Lorsque vous utilisez l’annotation @Transactional, Spring crée un proxy autour de l’objet cible. Ce proxy est responsable de la gestion des transactions. Lorsqu’une méthode annotée @Transactional est appelée, le proxy démarre une transaction avant que la méthode ne soit exécutée. Si la méthode se termine normalement, la transaction est validée (commit). Si une exception est levée, la transaction est annulée (rollback). Cependant, lorsque vous appelez une autre méthode publique de la même classe (c’est-à-dire une méthode interne), le proxy est contourné et l’annotation @Transactional est ignorée. Cela signifie que les règles de gestion des transactions que vous avez définies avec @Transactional ne s’appliquent pas aux appels de méthodes internes.

Ces concepts sont essentiels pour comprendre et maîtriser la gestion des transactions dans Spring. Ils vous permettent de contrôler avec précision comment les transactions sont gérées et comment les exceptions affectent le déroulement de vos transactions. En fin de compte, une bonne compréhension de ces concepts vous aidera à construire des applications plus robustes et fiables.

Aucun commentaire:

Enregistrer un commentaire

to criticize, to improve