Пътеводител за Джакарта EE JTA

1. Общ преглед

API за транзакции на Java , по-известен като JTA, е API за управление на транзакции в Java. Позволява ни да стартираме, ангажираме и връщаме транзакции по ресурсно-агностичен начин.

Истинската сила на JTA се крие в способността му да управлява множество ресурси (т.е. бази данни, услуги за съобщения) в една транзакция.

В този урок ще опознаем JTA на концептуално ниво и ще видим как бизнес кодът обикновено взаимодейства с JTA.

2. Универсален API и разпределена транзакция

JTA осигурява абстракция върху контрола на транзакциите (стартиране, фиксиране и връщане назад) към бизнес кода.

При липсата на тази абстракция ще трябва да се справим с отделните API на всеки тип ресурс.

Например трябва да се справим с ресурса на JDBC по този начин. По същия начин ресурсът на JMS може да има подобен, но несъвместим модел.

С JTA можем да управляваме множество ресурси от различен тип по последователен и координиран начин .

Като API, JTA дефинира интерфейси и семантика, които да бъдат внедрени от мениджърите на транзакции . Реализациите се предоставят от библиотеки като Narayana и Bitronix.

3. Примерна настройка на проект

Примерното заявление е много проста услуга за банково приложение. Имаме две услуги, BankAccountService и AuditService използват два различни бази данни . Тези независими бази данни трябва да бъдат координирани при започване на транзакция, фиксиране или връщане назад .

Като начало, нашият примерен проект използва Spring Boot за опростяване на конфигурацията:

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

И накрая, преди всеки метод за тестване, ние инициализираме AUDIT_LOG с празни данни и ACCOUNT в базата данни с 2 реда:

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

4. Декларативна демаркация на транзакция

Първият начин за работа с транзакции в JTA е използването на анотацията @Transactional . За по-сложно обяснение и конфигурация вижте тази статия.

Нека анотираме метода на фасадната услуга executeTranser () с @Transactional. Това инструктира мениджъра на транзакции да започне транзакция :

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

Тук методът executeTranser () извиква 2 различни услуги, AccountService и AuditService. Тези услуги използват 2 различни бази данни.

Когато executeTransfer () се върне, мениджърът на транзакции разпознава, че това е краят на транзакцията и ще се ангажира с двете бази данни :

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. Отмяна в декларативното разграничаване

В края на метода, executeTransfer () проверява салдото по сметката и изхвърля RuntimeException, ако източникът е недостатъчен:

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

Един необработено RuntimeException покрай първия @Transactional ще се върне назад сделката за двете бази данни . Всъщност изпълнението на превод със сума, по-голяма от салдото, ще доведе до откат :

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

5. Демаркация на програмна транзакция

Друг начин за контрол на JTA транзакцията е програмно чрез UserTransaction .

Сега нека модифицираме executeTransfer (), за да обработваме транзакцията ръчно:

userTransaction.begin(); bankAccountService.transfer(fromAccontId, toAccountId, amount); auditService.log(fromAccontId, toAccountId, amount); BigDecimal balance = bankAccountService.balanceOf(fromAccontId); if(balance.compareTo(BigDecimal.ZERO) < 0) { userTransaction.rollback(); throw new RuntimeException("Insufficient fund."); } else { userTransaction.commit(); }

В нашия пример методът begin () стартира нова транзакция. Ако проверката на баланса не успее, ние извикваме rollback (), който ще се върне и в двете бази данни. В противен случай извикването на commit () ангажира промените в двете бази данни .

Важно е да се отбележи, че и commit (), и rollback () завършват текущата транзакция.

В крайна сметка използването на програмно демаркация ни дава гъвкавостта на фино контролиран контрол на транзакциите.

6. Заключение

В тази статия обсъдихме проблема, който JTA се опитва да разреши. Примерите за кодове илюстрират контролиране на транзакцията с пояснения и програмно , като включват 2 транзакционни ресурси, които трябва да бъдат координирани в една транзакция.

Както обикновено, примерът с код може да бъде намерен в GitHub.