Vodič za Jakartu EE JTA

1. Pregled

Java Transaction API, poznatiji kao JTA, je API za upravljanje transakcijama u Javi. Omogućuje nam pokretanje, predavanje i vraćanje transakcija na agnotičan način.

Istinska snaga JTA-e leži u njenoj sposobnosti upravljanja višestrukim resursima (tj. Bazama podataka, uslugama razmjene poruka) u jednoj transakciji.

U ovom uputstvu upoznat ćemo JTA na konceptualnoj razini i vidjeti kako poslovni kod obično komunicira s JTA-om.

2. Univerzalni API i distribuirana transakcija

JTA pruža apstrakciju nad kontrolom transakcija (započinjanje, predavanje i vraćanje) na poslovni kod.

U nedostatku ove apstrakcije, morali bismo se nositi s pojedinačnim API-ima svake vrste resursa.

Na primjer, moramo se nositi s JDBC resursom poput ovog. Isto tako, JMS resurs može imati sličan, ali nespojiv model.

S JTA-om možemo upravljati višestrukim resursima različitih vrsta na dosljedan i koordiniran način.

Kao API, JTA definira sučelja i semantiku koju će implementirati voditelji transakcija. Implementacije pružaju knjižnice kao što su Narayana i Bitronix.

3. Uzorak postavljanja projekta

Uzorak aplikacije vrlo je jednostavna pozadinska usluga bankarske aplikacije. Imamo dvije usluge, BankAccountService i AuditService koristeći dvije različite baze podataka. Te neovisne baze podataka moraju se koordinirati nakon započinjanja, predavanja ili vraćanja transakcije.

Za početak, naš ogledni projekt koristi Spring Boot za pojednostavljivanje konfiguracije:

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

Napokon, prije svake metode ispitivanja inicijaliziramo AUDIT_LOG s praznim podacima i bazom podataka RAČUN s 2 reda:

+ ----------- + ---------------- + | ID | RAVNOTEŽA | + ----------- + ---------------- + | a0000001 | 1000 | | a0000002 | 2000 | + ----------- + ---------------- +

4. Deklarativno razgraničenje transakcije

Prvi način rada s transakcijama u JTA je upotreba @Transational bilješka. Za detaljnija objašnjenja i konfiguraciju pogledajte ovaj članak.

Napomenimo metodu fasadne usluge executeTranser () s @Transational. Ovo upućuje voditelj transakcija za početak transakcije:

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

Ovdje metoda executeTranser () poziva dvije različite usluge, AccountService i AuditService. Ove usluge koriste dvije različite baze podataka.

Kada executeTransfer () vraća se, the voditelj transakcija prepoznaje da je kraj transakcije i da će se obvezati na obje baze podataka:

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. Vraćanje u deklarativno razgraničenje

Na kraju metode, executeTransfer () provjerava stanje na računu i baca RuntimeException ako je izvorni fond nedovoljan:

@Transactional public void executeTransfer (String fromAccontId, String toAccountId, BigDecimal amount) {bankAccountService.transfer (fromAccontId, toAccountId, iznos); auditService.log (fromAccontId, toAccountId, iznos); BigDecimal saldo = bankAccountService.balanceOf (fromAccontId); if (balance.compareTo (BigDecimal.ZERO) <0) {baciti novo RuntimeException ("Nedovoljno sredstava."); }}

An neobrađen RuntimeException prošlost prva @Transational poništit će transakcijuu obje baze podataka. Zapravo, izvršavanje prijenosa s iznosom većim od salda prouzročit će povrat:

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

5. Programsko razgraničenje transakcija

Drugi način kontrole JTA transakcije je programski putem Korisnička transakcija.

Sada izmijenimo executeTransfer () za ručno rukovanje transakcijama:

userTransaction.begin (); bankAccountService.transfer (fromAccontId, toAccountId, iznos); auditService.log (fromAccontId, toAccountId, iznos); BigDecimal saldo = bankAccountService.balanceOf (fromAccontId); if (balance.compareTo (BigDecimal.ZERO) <0) {userTransaction.rollback (); baciti novi RuntimeException ("Nedovoljno sredstava."); } else {userTransaction.commit (); }

U našem primjeru, početi() metoda započinje novu transakciju. Ako provjera stanja ne uspije, zovemo vraćanje () koji će se vratiti u obje baze podataka. Inače, poziv na počiniti() uvodi promjene u obje baze podataka.

Važno je napomenuti da oboje počiniti() i vraćanje () završi trenutnu transakciju.

U konačnici, korištenje programskog razgraničenja daje nam fleksibilnost precizne kontrole transakcija.

6. Zaključak

U ovom smo članku razgovarali o problemu koji JTA pokušava riješiti. Primjeri koda ilustriraju kontrolu transakcija bilješkama i programski, koji uključuje 2 transakcijska resursa koja treba koordinirati u jednoj transakciji.

Kao i obično, primjer koda možete pronaći na GitHubu.