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.