Gids voor Jakarta EE JTA

1. Overzicht

Java Transaction API, beter bekend als JTA, is een API voor het beheren van transacties in Java. Het stelt ons in staat om transacties te starten, vast te leggen en terug te draaien op een resource-agnostische manier.

De echte kracht van JTA ligt in de mogelijkheid om meerdere bronnen (d.w.z. databases, berichtenservices) in één transactie te beheren.

In deze tutorial leren we JTA op conceptueel niveau kennen en zien we hoe bedrijfscode vaak samenwerkt met JTA.

2. Universele API en gedistribueerde transactie

JTA biedt een abstractie van transactiecontrole (begin, vastleggen en terugdraaien) naar bedrijfscode.

Zonder deze abstractie zouden we te maken hebben met de afzonderlijke API's van elk resourcetype.

We moeten bijvoorbeeld op deze manier omgaan met JDBC-bronnen. Evenzo kan een JMS-bron een vergelijkbaar, maar incompatibel model hebben.

Met JTA kunnen we dat beheer meerdere bronnen van verschillende typen op een consistente en gecoördineerde manier.

Als een API definieert JTA interfaces en semantiek die moeten worden geïmplementeerd door transactiemanagers. Implementaties worden geleverd door bibliotheken zoals Narayana en Bitronix.

3. Voorbeeld van projectconfiguratie

De voorbeeldtoepassing is een zeer eenvoudige back-endservice van een banktoepassing. We hebben twee services, de BankAccountService en AuditService met behulp van twee verschillende databases. Deze onafhankelijke databases moeten worden gecoördineerd bij het starten, vastleggen of terugdraaien van de transactie.

Om te beginnen gebruikt ons voorbeeldproject Spring Boot om de configuratie te vereenvoudigen:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE org.springframework.boot spring-boot-starter-jta-bitronix 

Ten slotte initialiseren we voor elke testmethode AUDIT_LOG met lege data en database ACCOUNT met 2 rijen:

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

4. Afbakening van declaratieve transacties

De eerste manier om met transacties in JTA te werken, is door gebruik te maken van de @Transactional annotatie. Zie dit artikel voor een meer uitgebreide uitleg en configuratie.

Laten we de gevelservicemethode annoteren executeTranser () met @Transactional. Dit instrueert het transactie manager om een ‚Äč‚Äčtransactie te starten:

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

Hier de methode executeTranser () belt 2 verschillende diensten, AccountService en AuditService. Deze services gebruiken 2 verschillende databases.

Wanneer executeTransfer () geeft terug, de transactie manager erkent dat dit het einde van de transactie is en zal zich committeren aan beide 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. Terugdraaien in declaratieve afbakening

Aan het einde van de methode, executeTransfer () controleert het rekeningsaldo en gooit RuntimeException als het bronfonds onvoldoende is:

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

Een onbehandeld RuntimeException voorbij de eerste @Transactional zal de transactie terugdraaiennaar beide databases. In feite zal het uitvoeren van een overboeking met een groter bedrag dan het saldo een terugdraaiing veroorzaken:

assertThatThrownBy (() -> {tellerService.executeTransfer ("a0000002", "a0000001", BigDecimal.valueOf (10000));}). hasMessage ("Onvoldoende fonds."); assertThat (accountService.balanceOf ("a0000001")). isEqualByComparingTo (BigDecimal.valueOf (1000)); assertThat (accountService.balanceOf ("a0000002")). isEqualByComparingTo (BigDecimal.valueOf (2000)); assertThat (auditServie.lastTransferLog ()). isNull ();

5. Programmatische transactie-afbakening

Een andere manier om JTA-transacties te beheren is programmatisch via UserTransaction.

Laten we nu aanpassen executeTransfer () om de transactie handmatig af te handelen:

userTransaction.begin (); bankAccountService.transfer (fromAccontId, toAccountId, bedrag); auditService.log (fromAccontId, toAccountId, bedrag); BigDecimal saldo = bankAccountService.balanceOf (fromAccontId); if (balance.compareTo (BigDecimal.ZERO) <0) {userTransaction.rollback (); gooi nieuwe RuntimeException ("Onvoldoende fonds."); } anders {userTransaction.commit (); }

In ons voorbeeld is de beginnen() methode start een nieuwe transactie. Als de saldo-validatie mislukt, bellen we terugrollen() die over beide databases zal terugdraaien. Anders, de oproep naar plegen () legt de wijzigingen vast in beide databases.

Het is belangrijk op te merken dat beide plegen () en terugrollen() beëindig de huidige transactie.

Uiteindelijk geeft het gebruik van programmatische afbakening ons de flexibiliteit van een fijnmazig transactiebeheer.

6. Conclusie

In dit artikel hebben we het probleem besproken dat JTA probeert op te lossen. De codevoorbeelden illustreren controlerende transactie met annotaties en programmatisch, waarbij 2 transactieresources betrokken zijn die in één transactie moeten worden gecoördineerd.

Zoals gewoonlijk is het codevoorbeeld te vinden op GitHub.