1. Overview
1.概述
Java Transaction API, more commonly known as JTA, is an API for managing transactions in Java. It allows us to start, commit and rollback transactions in a resource-agnostic way.
Java Transaction API,通常被称为JTA,是在Java中管理事务的API。它允许我们以一种资源无关的方式启动、提交和回滚事务。
The true power of JTA lies in its ability to manage multiple resources (i.e. databases, messaging services) in a single transaction.
JTA的真正力量在于它能够在一个事务中管理多个资源(即数据库、消息服务)。
In this tutorial, we’ll get to know JTA at the conceptual level and see how business code commonly interacts with JTA.
在本教程中,我们将在概念层面上了解JTA,并看看业务代码通常如何与JTA进行交互。
2. Universal API and Distributed Transaction
2.通用API和分布式事务
JTA provides an abstraction over transaction control (begin, commit and rollback) to business code.
JTA为业务代码提供了一个对事务控制(开始、提交和回滚)的抽象。
In the absence of this abstraction, we’d have to deal with the individual APIs of each resource type.
如果没有这种抽象,我们就必须处理每种资源类型的单独API。
For example, we need to deal with JDBC resource like this. Likewise, a JMS resource may have a similar but incompatible model.
例如,我们需要处理JDBC资源像这样的。同样,一个JMS资源可能有一个类似但不兼容的模型。
With JTA, we can manage multiple resources of different types in a consistent and coordinated manner.
通过JTA,我们可以以一致和协调的方式管理不同类型的多个资源。
As an API, JTA defines interfaces and semantics to be implemented by transaction managers. Implementations are provided by libraries such as Narayana and Atomikos.
作为一个 API,JTA 定义了接口和语义,以便由交易管理器来实现。实现由Narayana和Atomikos等库提供。
3. Sample Project Setup
3.项目设置样本
The sample application is a very simple back-end service of a banking application. We have two services, the BankAccountService and AuditService using two different databases. These independent databases need to be coordinated upon transaction begin, commit or rollback.
该示例应用程序是一个非常简单的银行应用程序的后端服务。我们有两个服务,BankAccountService和AuditService使用两个不同的数据库。 这些独立的数据库需要在事务开始、提交或回滚时得到协调。
To begin with, our sample project uses Spring Boot to simplify configuration:
首先,我们的示例项目使用Spring Boot来简化配置。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
Finally, before each test method we initialize AUDIT_LOG with empty data and database ACCOUNT with 2 rows:
最后,在每个测试方法之前,我们用空数据初始化AUDIT_LOG,数据库ACCOUNT有2行。
+-----------+----------------+
| ID | BALANCE |
+-----------+----------------+
| a0000001 | 1000 |
| a0000002 | 2000 |
+-----------+----------------+
4. Declarative Transaction Demarcation
4.声明式事务分界
The first way of working with transactions in JTA is with the use of the @Transactional annotation. For a more elaborate explanation and configuration see this article.
在JTA中处理事务的第一种方式是使用@Transactional注解。关于更详细的解释和配置,请参阅这篇文章。
Let’s annotate the facade service method executeTranser() with @Transactional. This instructs the transaction manager to begin a transaction:
让我们用@Transactional>来注解facade服务方法executeTranser()。这将指示交易管理器开始一个交易:。
@Transactional
public void executeTransfer(String fromAccontId, String toAccountId, BigDecimal amount) {
bankAccountService.transfer(fromAccontId, toAccountId, amount);
auditService.log(fromAccontId, toAccountId, amount);
...
}
Here the method executeTranser() calls 2 different services, AccountService and AuditService. These services use 2 different databases.
在这里,方法executeTranser()调用2个不同的服务,AccountService和AuditService。这些服务使用2个不同的数据库。
When executeTransfer() returns, the transaction manager recognizes that it is the end of the transaction and will commit to both databases:
当executeTransfer()返回时,transaction manager认识到这是事务的结束,并将向两个数据库提交。
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. Rolling Back in Declarative Demarcation
4.1.声明性划界中的回滚
At the end of the method, executeTransfer() checks the account balance and throws RuntimeException if the source fund is insufficient:
在方法的最后,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.");
}
}
An unhandled RuntimeException past the first @Transactional will rollback the transaction to both databases. In effect, executing a transfer with an amount bigger than the balance will cause a rollback:
一个未处理的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. Programmatic Transaction Demarcation
5.程序性事务的划分
Another way to control JTA transaction is programmatically via UserTransaction.
另一种控制JTA事务的方法是通过UserTransaction以编程方式进行。
Now let’s modify executeTransfer() to handle transaction manually:
现在让我们修改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();
}
In our example, the begin() method starts a new transaction. If the balance validation fails, we call rollback() which will rollback over both databases. Otherwise, the call to commit() commits the changes to both databases.
在我们的例子中,begin()方法开始一个新的交易。如果余额验证失败,我们调用rollback(),这将在两个数据库上回滚。否则,调用commit()将更改提交给两个数据库。
It’s important to note that both commit() and rollback() end the current transaction.
值得注意的是,commit()和rollback()都会结束当前事务。
Ultimately, using programmatic demarcation gives us the flexibility of fine-grained transaction control.
最终,使用程序化分界给了我们细粒度事务控制的灵活性。
6. Conclusion
6.结论
In this article, we discussed the problem JTA tries to resolve. The code examples illustrate controlling transaction with annotations and programmatically, involving 2 transactional resources that need to be coordinated in a single transaction.
在这篇文章中,我们讨论了JTA试图解决的问题。代码示例展示了用注解和编程方式控制事务,涉及2个需要在一个事务中协调的事务性资源。
As usual, the code example can be found over on GitHub.
像往常一样,代码示例可以在GitHub上找到over。