Руководство по Java EE JTA

Руководство по Java EE JTA

1. обзор

API транзакций Java, более известный как JTA,is an API for managing transactions in Java.. Он позволяет нам запускать, фиксировать и откатывать транзакции без привязки к ресурсам.

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

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

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

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

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

Например, нам нужно иметь дело с ресурсом JDBClike this. Точно так же ресурс JMS может иметьsimilar but incompatible model.

С JTA мы можемmanage multiple resources of different types in a consistent and coordinated manner.

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

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

Пример приложения - это очень простой серверный сервис банковского приложения. У нас есть две службы,BankAccountService andAuditService, использующие две разные базы данныхThese independent databases need to be coordinated upon transaction begin, commit or rollback.

Для начала наш пример проекта использует Spring Boot для упрощения настройки:


    org.springframework.boot
    spring-boot-starter-parent
    2.0.4.RELEASE



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

Наконец, перед каждым методом тестирования мы инициализируемAUDIT_LOG пустыми данными и базу данныхACCOUNT двумя строками:

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

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

Первый способ работы с транзакциями в JTA - использование аннотации@Transactional. Для более подробного объяснения и конфигурации см.this article.

Давайте аннотируем метод службы фасадаexecuteTranser() с помощью@Transactional. . Это дает командуtransaction manager начать транзакцию:

@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() возвращается,the transaction manager recognizes that it is the end of the transaction and will commit to both databases:

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.");
    }
}

unhandled RuntimeException past the first @Transactional will rollback the transactionto both databases. Фактически, выполнение перевода на сумму, превышающую баланс, приведет к откату:

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(), который откатит обе базы данных. В противном случаеthe call to commit()commits the changes to both databases.

Важно отметить, что какcommit(), так иrollback() завершают текущую транзакцию.

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

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

В этой статье мы обсудили проблему, которую JTA пытается решить. Примеры кодаillustrate controlling transaction with annotations and programmatically, включающие 2 транзакционных ресурса, которые необходимо скоординировать в одной транзакции.

Как обычно, можно найти пример кодаover on GitHub.