Guide til Jakarta EE JTA

1. Oversikt

Java Transaction API, mer kjent som JTA, er et API for å administrere transaksjoner i Java. Det lar oss starte, begå og tilbakebetale transaksjoner på en ressursagnostisk måte.

Den virkelige kraften til JTA ligger i evnen til å administrere flere ressurser (dvs. databaser, meldingstjenester) i en enkelt transaksjon.

I denne opplæringen vil vi bli kjent med JTA på det konseptuelle nivået og se hvordan forretningskode ofte samhandler med JTA.

2. Universal API og distribuert transaksjon

JTA gir en abstraksjon over transaksjonskontroll (start, begå og tilbakeføring) til forretningskode.

I fravær av denne abstraksjonen, må vi håndtere de individuelle API-ene for hver ressurstype.

For eksempel trenger vi å håndtere JDBC-ressurser som denne. På samme måte kan en JMS-ressurs ha en lignende, men inkompatibel modell.

Med JTA kan vi administrere flere ressurser av forskjellige typer på en konsistent og koordinert måte.

Som API definerer JTA grensesnitt og semantikk som skal implementeres av transaksjonsledere. Implementeringer leveres av biblioteker som Narayana og Bitronix.

3. Eksempel på prosjektoppsett

Eksempelapplikasjonen er en veldig enkel back-end-tjeneste for en banksøknad. Vi har to tjenester, den BankAccountService og AuditService ved hjelp av to forskjellige databaser. Disse uavhengige databasene må koordineres når transaksjonsstart, forpliktelse eller tilbakeføring.

Til å begynne med bruker vårt prøveprosjekt Spring Boot for å forenkle konfigurasjonen:

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

Til slutt, før hver testmetode initialiserer vi AUDIT_LOG med tomme data og database REGNSKAP med 2 rader:

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

4. Deklarativ transaksjonsavgrensning

Den første måten å jobbe med transaksjoner i JTA på er å bruke @Transaksjonell kommentar. For en mer utførlig forklaring og konfigurasjon, se denne artikkelen.

La oss kommentere fasadeservicemetoden executeTranser () med @Transaksjonell. Dette instruerer transaksjonsleder for å starte en transaksjon:

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

Her metoden executeTranser () ringer to forskjellige tjenester, AccountService og AuditService. Disse tjenestene bruker to forskjellige databaser.

Når executeTransfer () returnerer, de transaksjonsleder anerkjenner at det er slutten på transaksjonen og vil forplikte seg til begge databasene:

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. Rulling tilbake i deklarativ avgrensning

På slutten av metoden, executeTransfer () sjekker kontosaldoen og kaster RuntimeException hvis kildefondet er utilstrekkelig:

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

An uhåndtert RuntimeException forbi den første @Transaksjonell vil tilbakebetale transaksjonentil begge databasene. Faktisk vil gjennomføring av en overføring med et beløp større enn balansen føre til tilbakeføring:

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

5. Programmatisk avgrensning av transaksjoner

En annen måte å kontrollere JTA-transaksjonen på er programmatisk via UserTransaction.

La oss nå endre executeTransfer () å håndtere transaksjonen manuelt:

userTransaction.begin (); bankAccountService.transfer (fraAccontId, tilAccountId, beløp); auditService.log (fraAccontId, tilAccountId, beløp); BigDecimal-saldo = bankAccountService.balanceOf (fromAccontId); hvis (balance.compareTo (BigDecimal.ZERO) <0) {userTransaction.rollback (); kaste nytt RuntimeException ("Utilstrekkelig fond."); } annet {userTransaction.commit (); }

I vårt eksempel, den begynne() metoden starter en ny transaksjon. Hvis balansevalidering mislykkes, ringer vi tilbakeføring () som vil tilbakestilles over begge databasene. Ellers, samtalen til begå() forplikter endringene til begge databasene.

Det er viktig å merke seg at begge begå() og tilbakeføring () avslutte den nåværende transaksjonen.

Til syvende og sist gir bruk av programmatisk avgrensning oss fleksibiliteten i finkornet transaksjonskontroll.

6. Konklusjon

I denne artikkelen diskuterte vi problemet JTA prøver å løse. Kodeeksemplene illustrere kontrollerende transaksjon med merknader og programmatisk, som involverer to transaksjonsressurser som må koordineres i en enkelt transaksjon.

Som vanlig kan kodeeksemplet finnes på GitHub.


$config[zx-auto] not found$config[zx-overlay] not found