Guide til Jakarta EE JTA

1. Oversigt

Java Transaction API, mere almindeligt kendt som JTA, er en API til styring af transaktioner i Java. Det giver os mulighed for at starte, begå og returnere transaktioner på en ressource-agnostisk måde.

Den sande kraft ved JTA ligger i dens evne til at administrere flere ressourcer (dvs. databaser, messaging-tjenester) i en enkelt transaktion.

I denne vejledning lærer vi JTA at kende på det konceptuelle niveau og ser, hvordan forretningskode ofte interagerer med JTA.

2. Universal API og distribueret transaktion

JTA giver en abstraktion over transaktionskontrol (start, begå og tilbageførsel) til forretningskode.

I mangel af denne abstraktion ville vi skulle beskæftige os med de individuelle API'er for hver ressourcetype.

For eksempel skal vi håndtere JDBC-ressourcer som denne. Ligeledes kan en JMS-ressource have en lignende, men inkompatibel model.

Med JTA kan vi administrere flere ressourcer af forskellige typer på en ensartet og koordineret måde.

Som API definerer JTA grænseflader og semantik, der skal implementeres af transaktionschefer. Implementeringer leveres af biblioteker som Narayana og Bitronix.

3. Eksempel på projektopsætning

Eksempelapplikationen er en meget enkel back-end-service af en bankapplikation. Vi har to tjenester, den BankAccountService og AuditService ved hjælp af to forskellige databaser. Disse uafhængige databaser skal koordineres, når transaktionen begynder, begås eller tilbageføres.

Til at begynde med bruger vores prøveprojekt Spring Boot til at forenkle konfigurationen:

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

Endelig, før hver testmetode initialiseres AUDIT_LOG med tomme data og database KONTO med 2 rækker:

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

4. Deklarativ transaktionsafgrænsning

Den første måde at arbejde med transaktioner i JTA på er at bruge @Transaktionel kommentar. For en mere detaljeret forklaring og konfiguration se denne artikel.

Lad os kommentere facadeservicemetoden executeTranser () med @Transaktionel. Dette instruerer transaktionschef at starte en transaktion:

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

Her er metoden executeTranser () kalder 2 forskellige tjenester, Kontoservice og AuditService. Disse tjenester bruger 2 forskellige databaser.

Hvornår executeTransfer () vender tilbage, det transaktionschef anerkender, at det er slutningen af ​​transaktionen og vil forpligte sig til begge databaser:

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. Tilbagevenden i deklarativ afgrænsning

I slutningen af ​​metoden, executeTransfer () kontrollerer kontosaldoen og kaster RuntimeException hvis kildefonden ikke er tilstrækkelig:

@Transactional public void executeTransfer (String fromAccontId, String toAccountId, BigDecimal amount) {bankAccountService.transfer (fromAccontId, toAccountId, amount); auditService.log (fraAccontId, tilAccountId, beløb); BigDecimal saldo = bankAccountService.balanceOf (fromAccontId); if (balance.compareTo (BigDecimal.ZERO) <0) {kast ny RuntimeException ("Utilstrækkelig fond."); }}

En uhåndteret RuntimeException forbi den første @Transaktionel vil tilbageføre transaktionentil begge databaser. I virkeligheden vil udførelse af en overførsel med et beløb større end saldoen medføre tilbageførsel:

assertThatThrownBy (() -> {tellerService.executeTransfer ("a0000002", "a0000001", BigDecimal.valueOf (10000));}). hasMessage ("Utilstrækkelig fond."); assertThat (accountService.balanceOf ("a0000001")). isEqualByComparingTo (BigDecimal.valueOf (1000)); assertThat (accountService.balanceOf ("a0000002")). isEqualByComparingTo (BigDecimal.valueOf (2000)); assertThat (auditServie.lastTransferLog ()). er Null ();

5. Afgrænsning af programmatisk transaktion

En anden måde at kontrollere JTA-transaktion på er programmatisk via UserTransaction.

Lad os nu ændre executeTransfer () at håndtere transaktion manuelt:

userTransaction.begin (); bankAccountService.transfer (fraAccontId, tilAccountId, beløb); auditService.log (fraAccontId, tilAccountId, beløb); BigDecimal saldo = bankAccountService.balanceOf (fromAccontId); hvis (balance.compareTo (BigDecimal.ZERO) <0) {userTransaction.rollback (); smid ny RuntimeException ("Utilstrækkelig fond."); } andet {userTransaction.commit (); }

I vores eksempel er begynde() metoden starter en ny transaktion. Hvis balancevalidering mislykkes, ringer vi til tilbageførsel () som vender tilbage over begge databaser. Ellers, opkaldet til begå() forpligter ændringerne til begge databaser.

Det er vigtigt at bemærke, at begge dele begå() og tilbageførsel () afslut den aktuelle transaktion.

I sidste ende giver brug af programmatisk afgrænsning os fleksibiliteten i finkornet transaktionskontrol.

6. Konklusion

I denne artikel diskuterede vi problemet JTA forsøger at løse. Kodeeksemplerne illustrerer kontrollerende transaktion med annoteringer og programmatisk, der involverer 2 transaktionsressourcer, der skal koordineres i en enkelt transaktion.

Som sædvanligt kan kodeeksemplet findes på GitHub.