Рубрики
Без рубрики

Путеводитель по Джакарте EE JTA

Узнайте, как выполнять транзакции с несколькими ресурсами с помощью Jakarta EE JTA.

Автор оригинала: baeldung.

1. Обзор

API транзакций Java, более известный как JTA, – это API для управления транзакциями в Java. Это позволяет нам запускать, фиксировать и откатывать транзакции без учета ресурсов.

Истинная мощь JTA заключается в ее способности управлять несколькими ресурсами (т. е. базами данных, службами обмена сообщениями) в одной транзакции.

В этом уроке мы познакомимся с JTA на концептуальном уровне и посмотрим, как бизнес-код обычно взаимодействует с JTA.

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

JTA обеспечивает абстракцию над управлением транзакциями (начало, фиксация и откат) в бизнес-коде.

В отсутствие этой абстракции нам пришлось бы иметь дело с отдельными API-интерфейсами каждого типа ресурсов.

Например, нам нужно иметь дело с ресурсом JDBC вот так . Аналогично, ресурс JMS может иметь аналогичную, но несовместимую модель .

С помощью JTA мы можем управлять несколькими ресурсами различных типов согласованным и скоординированным образом .

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

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

Пример приложения представляет собой очень простой серверный сервис банковского приложения. У нас есть две службы: Служба банковских счетов и Служба аудита с использованием двух разных баз данных . Эти независимые базы данных должны быть скоординированы при начале транзакции, фиксации или откате .

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


    org.springframework.boot
    spring-boot-starter-parent
    2.4.0



    org.springframework.boot
    spring-boot-starter-jta-bitronix

Наконец, перед каждым методом тестирования мы инициализируем AUDIT_LOG с пустыми данными и базой данных УЧЕТНОЙ ЗАПИСЬЮ с 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);
    ...
}

Здесь метод execute Transfer() вызывает 2 разные службы, Службу учетных записей и Службу аудита. Эти службы используют 2 разные базы данных.

Когда execute Transfer() возвращается, менеджер транзакций распознает, что это конец транзакции, и зафиксирует обе базы данных :

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 .

Теперь давайте изменим execute Transfer() для обработки транзакции вручную:

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 .