Guide de Java EE JTA

Guide de Java EE JTA

1. Vue d'ensemble

API de transaction Java, plus connue sous le nom de JTA,is an API for managing transactions in Java. Elle nous permet de démarrer, de valider et d'annuler des transactions de manière indépendante des ressources.

Le véritable pouvoir de JTA réside dans sa capacité à gérer plusieurs ressources (c.-à-d. bases de données, services de messagerie) en une seule transaction.

Dans ce didacticiel, nous allons découvrir JTA au niveau conceptuel et voir comment le code métier interagit généralement avec JTA.

2. API universelle et transaction distribuée

JTA fournit une abstraction sur le contrôle des transactions (début, validation et restauration) en code métier.

En l'absence de cette abstraction, nous devrons nous occuper des API individuelles de chaque type de ressource.

Par exemple, nous devons gérer la ressource JDBClike this. De même, une ressource JMS peut avoir unsimilar but incompatible model.

Avec JTA, nous pouvonsmanage multiple resources of different types in a consistent and coordinated manner.

En tant qu'API, JTA définit les interfaces et la sémantique à implémenter partransaction managers. Les implémentations sont fournies par des bibliothèques telles queNarayana etBitronix.

3. Exemple de configuration de projet

L'exemple d'application est un service back-end très simple d'une application bancaire. Nous avons deux services, lesBankAccountService andAuditService utilisant deux bases de données différentesThese independent databases need to be coordinated upon transaction begin, commit or rollback.

Pour commencer, notre exemple de projet utilise Spring Boot pour simplifier la configuration:


    org.springframework.boot
    spring-boot-starter-parent
    2.0.4.RELEASE



    org.springframework.boot
    spring-boot-starter-jta-bitronix

Enfin, avant chaque méthode de test, nous initialisonsAUDIT_LOG avec des données vides et la base de donnéesACCOUNT avec 2 lignes:

+-----------+----------------+
| ID        |  BALANCE       |
+-----------+----------------+
| a0000001  |  1000          |
| a0000002  |  2000          |
+-----------+----------------+

4. Démarcation de transaction déclarative

La première façon de travailler avec les transactions dans JTA consiste à utiliser l'annotation@Transactional. Pour une explication et une configuration plus élaborées, voirthis article.

Annotons la méthode de service de façadeexecuteTranser() avec@Transactional.  Ceci indique auxtransaction manager de commencer une transaction:

@Transactional
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
    bankAccountService.transfer(fromAccontId, toAccountId, amount);
    auditService.log(fromAccontId, toAccountId, amount);
    ...
}

Ici la méthodeexecuteTranser() appelle 2 services différents,AccountService etAuditService. Ces services utilisent 2 bases de données différentes.

LorsqueexecuteTransfer() retourne,the transaction manager recognizes that it is the end of the transaction and will commit to both databases:

tellerService.executeTransfer("a0000001", "a0000002", BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000001"))
  .isEqualByComparingTo(BigDecimal.valueOf(500));
assertThat(accountService.balanceOf("a0000002"))
  .isEqualByComparingTo(BigDecimal.valueOf(2500));

TransferLog lastTransferLog = auditService
  .lastTransferLog();
assertThat(lastTransferLog)
  .isNotNull();
assertThat(lastTransferLog.getFromAccountId())
  .isEqualTo("a0000001");
assertThat(lastTransferLog.getToAccountId())
  .isEqualTo("a0000002");
assertThat(lastTransferLog.getAmount())
  .isEqualByComparingTo(BigDecimal.valueOf(500));

4.1. Revenir en arrière dans la démarcation déclarative

À la fin de la méthode,executeTransfer() vérifie le solde du compte et lanceRuntimeException si le fonds source est insuffisant:

@Transactional
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
    bankAccountService.transfer(fromAccontId, toAccountId, amount);
    auditService.log(fromAccontId, toAccountId, amount);
    BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
    if(balance.compareTo(BigDecimal.ZERO) < 0) {
        throw new RuntimeException("Insufficient fund.");
    }
}

Ununhandled RuntimeException past the first @Transactional will rollback the transactionto both databases. En effet, exécuter un virement d'un montant supérieur au solde entraînera un rollback:

assertThatThrownBy(() -> {
    tellerService.executeTransfer("a0000002", "a0000001", BigDecimal.valueOf(10000));
}).hasMessage("Insufficient fund.");

assertThat(accountService.balanceOf("a0000001")).isEqualByComparingTo(BigDecimal.valueOf(1000));
assertThat(accountService.balanceOf("a0000002")).isEqualByComparingTo(BigDecimal.valueOf(2000));
assertThat(auditServie.lastTransferLog()).isNull();

5. Démarcation de transaction programmatique

Un autre moyen de contrôler la transaction JTA est par programme viaUserTransaction.

Modifions maintenantexecuteTransfer() pour gérer la transaction manuellement:

userTransaction.begin();

bankAccountService.transfer(fromAccontId, toAccountId, amount);
auditService.log(fromAccontId, toAccountId, amount);
BigDecimal balance = bankAccountService.balanceOf(fromAccontId);
if(balance.compareTo(BigDecimal.ZERO) < 0) {
    userTransaction.rollback();
    throw new RuntimeException("Insufficient fund.");
} else {
    userTransaction.commit();
}

Dans notre exemple, la méthodebegin() démarre une nouvelle transaction. Si la validation du solde échoue, nous appelonsrollback() qui annulera les deux bases de données. Sinon,the call to commit()commits the changes to both databases.

Il est important de noter quecommit() etrollback() mettent fin à la transaction en cours.

En fin de compte, l’utilisation de la démarcation programmatique nous donne la flexibilité d’un contrôle de transaction fin.

6. Conclusion

Dans cet article, nous avons discuté du problème que JTA tente de résoudre. Les exemples de codeillustrate controlling transaction with annotations and programmatically, impliquant 2 ressources transactionnelles qui doivent être coordonnées en une seule transaction.

Comme d'habitude, l'exemple de code peut être trouvéover on GitHub.