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