Transactions programmatiques dans le cadre Spring TestContext

Transactions programmatiques dans le cadre Spring TestContext

1. introduction

Spring offre un excellent support pour la gestion déclarative des transactions dansapplication code ainsi que dansintegration tests.

Cependant, nous pouvons parfois avoir besoin d'un contrôle fin des limites des transactions.

Dans cet article, nous verronshow to programmatically interact with automatic transactions set up by Spring in transactional tests.

2. Conditions préalables

Supposons que nous ayons des tests d'intégration dans notre application Spring.

Plus précisément, nous envisageons des tests qui interagissent avec une base de données, par exemple pour vérifier que notre couche de persistance se comporte correctement.

Considérons une classe de test standard - annotée comme transactionnelle:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { HibernateConf.class })
@Transactional
public class HibernateBootstrapIntegrationTest { ... }

Dans un tel test,every test method is wrapped in a transaction, which gets rolled back when the method exits.

Bien entendu, il est également possible d’annoter uniquement des méthodes spécifiques. Tout ce dont nous parlerons dans cet article s'applique également à ce scénario.

3. La classeTestTransaction

Nous passerons le reste de l'article à discuter d'une seule classe:org.springframework.test.context.transaction.TestTransaction.

Il s'agit d'une classe utilitaire avec quelques méthodes statiques que nous pouvons utiliser pour interagir avec des transactions dans nos tests.

Chaque méthode interagit avec la seule transaction en cours mise en place lors de l'exécution d'une méthode de test.

3.1. Vérification de l'état de la transaction en cours

Lors des tests, nous vérifions souvent que les choses sont dans l'état où elles sont supposées être.

Par conséquent, nous pourrions vouloir vérifier s'il existe une transaction actuellement active:

assertTrue(TestTransaction.isActive());

Ou, nous pourrions être intéressés pour vérifier si la transaction en cours est marquée pour une annulation ou non:

assertTrue(TestTransaction.isFlaggedForRollback());

Si c'est le cas, Spring l'annulera juste avant la fin, automatiquement ou par programme. Sinon, il le validera juste avant de le fermer.

3.2. Marquer une transaction pour validation ou annulation

Nous pouvons modifier par programme la stratégie pour valider ou annuler la transaction avant de la fermer:

TestTransaction.flagForCommit();
TestTransaction.flagForRollback();

Normalement, les transactions dans les tests sont marquées pour une annulation au démarrage. Cependant, si la méthode a une annotation@Commit, ils commencent à être marqués pour validation à la place:

@Test
@Commit
public void testFlagForCommit() {
    assertFalse(TestTransaction.isFlaggedForRollback());
}

Notez que ces méthodes signalent simplement la transaction, comme leur nom l'indique. Autrement dit,the transaction isn’t committed or rolled back immediately, but only just before it ends.

3.3. Démarrage et fin d'une transaction

Pour valider ou annuler une transaction, nous laissons la méthode sortir ou la terminons explicitement:

TestTransaction.end();

Si plus tard, nous voulons à nouveau interagir avec la base de données, nous devons lancer une nouvelle transaction:

TestTransaction.start();

Notez que la nouvelle transaction sera marquée pour une annulation (ou une validation) selon la valeur par défaut de la méthode. En d'autres termes, les appels précédents àflagFor… n'ont aucun effet sur les nouvelles transactions.

4. Quelques détails d'implémentation

TestTransaction n'a rien de magique. Nous allons maintenant examiner sa mise en œuvre pour en savoir un peu plus sur les transactions lors des tests avec Spring.

Nous pouvons voir que ses quelques méthodes obtiennent simplement un accès à la transaction en cours et encapsulent certaines de ses fonctionnalités.

4.1. D'oùTestTransaction obtient-il la transaction actuelle?

Passons directement au code:

TransactionContext transactionContext
  = TransactionContextHolder.getCurrentTransactionContext();

TransactionContextHolder est juste un wrapper statique autour d'unThreadLocal contenant unTransactionContext.

4.2. Qui définit le contexte de thread local?

Si nous regardons qui appelle la méthodesetCurrentTransactionContext, nous trouverons qu'il n'y a qu'un seul appelant:TransactionalTestExecutionListener.beforeTestMethod.

TransactionalTestExecutionListener est l'écouteur que Springs configure automatiquement sur les tests qui sont annotés@Transactional.

Notez queTransactionContext ne contient aucune référence à une transaction réelle; il s'agit plutôt d'une façade sur lesPlatformTransactionManager.

Oui, ce code est très complexe et abstrait. Ce sont souvent les éléments essentiels du cadre de Spring.

Il est intéressant de voir comment, sous la complexité, Spring ne fait aucune magie noire - juste beaucoup de comptabilité, de plomberie, de gestion des exceptions, etc.

5. Conclusions

Dans ce rapide didacticiel, nous avons vu comment interagir par programmation avec les transactions dans les tests Spring.

L'implémentation de tous ces exemples peut être trouvée dansthe GitHub project - c'est un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.