Uploaded by hex

Guide to Jakarta EE JTA Baeldung

advertisement
2023. 06. 29. 17:52
Guide to Jakarta EE JTA | Baeldung
Guide to Jakarta EE JTA
Last updated: June 7, 2022
1. Overview
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.
The true power of JTA lies in its ability to manage multiple resources (i.e.
databases, messaging services) in a single transaction.
In this tutorial, we'll get to know JTA at the conceptual level and see how
business code commonly interacts with JTA.
2. Universal API and Distributed Transaction
JTA provides an abstraction over transaction control (begin, commit and
rollback) to business code.
In the absence of this abstraction, we'd have to deal with the individual APIs
of each resource type.
For example, we need to deal with JDBC resource like this
(https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html).
Likewise, a JMS resource may have a similar but incompatible model
(https://docs.oracle.com/cd/E19798-01/821-1841/bncgh/index.html).
With JTA, we can manage multiple resources of different types in a
consistent and coordinated manner.
As an API, JTA defines interfaces and semantics to be implemented
by transaction managers. Implementations are provided by libraries such as
Narayana (http://narayana.io) and Atomikos (/java-atomikos).
/freestarcom/?
https://www.baeldung.com/jee-jta
1/5
2023. 06. 29. 17:52
Guide to Jakarta EE JTA | Baeldung
/freestar.com/?
utm_source=baeldung.com&utm_content=baeldung_adhesion)
3. Sample Project Setup
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.
To begin with, our sample project uses Spring Boot to simplify configuration:
<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:
+-----------+----------------+
| ID
| BALANCE
|
+-----------+----------------+
| a0000001 | 1000
|
| a0000002 | 2000
|
+-----------+----------------+
4. Declarative Transaction Demarcation
The first way of working with transactions in JTA is with the use of the
@Transactional
(https://docs.oracle.com/javaee/7/api/javax/transaction/Transactional.ht
ml) annotation. For a more elaborate explanation and configuration see this
article (/transaction-configuration-with-jpa-and-spring).
Let's annotate the facade service method executeTranser() with
@Transactional. This instructs the transaction manager to begin a
transaction:
/freestarcom/?
https://www.baeldung.com/jee-jta
2/5
2023. 06. 29. 17:52
Guide to Jakarta EE JTA | Baeldung
/freestar.com/?
@Transactional
utm_source=baeldung.com&utm_content=baeldung_adhesion)
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.
When executeTransfer() returns, 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. Rolling Back in Declarative Demarcation
At the end of the method, executeTransfer() checks the account balance
and throws RuntimeException if the source fund is insufficient:
/freestarcom/?
https://www.baeldung.com/jee-jta
3/5
2023. 06. 29. 17:52
Guide to Jakarta EE JTA | Baeldung
/freestar.com/?
@Transactional
utm_source=baeldung.com&utm_content=baeldung_adhesion)
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:
assertThatThrownBy(() -> {
tellerService.executeTransfer("a0000002", "a0000001",
BigDecimal.valueOf(10000));
}).hasMessage("Insufficient fund.");
assertThat(accountService.balanceOf("a0000001")).isEqualByComparing
To(BigDecimal.valueOf(1000));
assertThat(accountService.balanceOf("a0000002")).isEqualByComparing
To(BigDecimal.valueOf(2000));
assertThat(auditServie.lastTransferLog()).isNull();
5. Programmatic Transaction Demarcation
Another way to control JTA transaction is programmatically
via UserTransaction (https://javaee.github.io/javaeespec/javadocs/javax/transaction/UserTransaction.html).
Now let's modify executeTransfer() to handle transaction manually:
/freestarcom/?
https://www.baeldung.com/jee-jta
4/5
2023. 06. 29. 17:52
Guide to Jakarta EE JTA | Baeldung
/freestar.com/?
userTransaction.begin();
utm_source=baeldung.com&utm_content=baeldung_adhesion)
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.
It's important to note that both commit() and rollback() end the current
transaction.
Ultimately, using programmatic demarcation gives us the flexibility of finegrained transaction control.
6. Conclusion
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.
As usual, the code example can be found over on GitHub
(https://github.com/eugenp/tutorials/tree/master/persistencemodules/spring-persistence-simple).
Comments are closed on this article!
/freestarcom/?
https://www.baeldung.com/jee-jta
5/5
Download