Master Distributed Transactions in Spring Boot 3 with Atomikos – Full Code Guide

This article walks through integrating Atomikos with Spring Boot 3 to implement JTA‑based distributed transactions, covering the concepts of Atomikos and JTA, detailed configuration of multiple data sources, domain and repository definitions, service implementation, and comprehensive test cases with expected outcomes.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Distributed Transactions in Spring Boot 3 with Atomikos – Full Code Guide

Spring Boot 3 practical case collection – updated to 139 examples, continuously maintained as a free resource.

1. Introduction

This article demonstrates how to integrate Atomikos with Spring Boot to achieve distributed transactions using JTA.

1.1 What is Atomikos

Atomikos is a third‑party transaction manager that implements the Java Transaction API (JTA). It provides transaction coordination, lifecycle management, failure recovery, and performance optimizations.

JTA : Standard Java interfaces for managing distributed transactions.

Atomikos : A concrete JTA implementation offering interfaces such as javax.transaction.UserTransaction and javax.transaction.TransactionManager.

1.2 What is JTA

JTA (Java Transaction API) enables distributed transaction management across multiple resources (databases, message queues). Core concepts include distributed transactions, the XA protocol, transaction managers, and resource managers.

Distributed transactions – coordinated across multiple resources.

XA protocol – two‑phase commit standard.

Transaction manager – coordinates global transactions.

Resource manager – XA‑compatible drivers (e.g., MySQL).

Spring and Spring Boot provide built‑in support for JTA, allowing developers to declare transaction boundaries without writing low‑level transaction code.

2. Practical Example

2.1 Environment

Spring Boot 2.7.18, Atomikos 4.x, MySQL 5.7.

2.2 Project Dependency

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

2.3 Domain Objects

Two entities are placed in separate packages.

// pkg: com.pack.domain.customer
@Entity
@Table(name = "customer")
public class Customer {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  @Column(name = "name", nullable = false)
  private String name;
  @Column(name = "age", nullable = false)
  private Integer age;
}

// pkg: com.pack.domain.order
@Entity
@Table(name = "orders")
public class Order {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer id;
  @Column(name = "code", nullable = false)
  private Integer code;
  @Column(name = "quantity", nullable = false)
  private Integer quantity;
}

2.4 Repository Interfaces

// pkg: com.pack.repository.customer
public interface CustomerRepository extends JpaRepository<Customer, Integer> {}

// pkg: com.pack.repository.order
public interface OrderRepository extends JpaRepository<Order, Integer> {}

2.5 Service Layer

// pkg: com.pack.service.customer
@Service
public class CustomerService {
  @Resource
  private CustomerRepository customerRepository;

  @Transactional
  public void save(Customer customer) {
    customerRepository.save(customer);
  }
}

// pkg: com.pack.service.order
@Service
public class OrderService {
  @Resource
  private OrderRepository orderRepository;

  @Transactional
  public void save(Order order) {
    orderRepository.save(order);
    throw new RuntimeException("订单发生异常");
  }
}

2.6 Configuration for Each Data Source

Customer Data Source

@ConfigurationProperties(prefix = "pack.datasource.customer")
public class CustomerDataSourceProperties {
  private String jdbcUrl;
  private String username;
  private String password;
}

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(
  basePackages = "com.pack.repository.customer",
  entityManagerFactoryRef = "customerEntityManager",
  transactionManagerRef = "transactionManager")
@EnableConfigurationProperties(CustomerDataSourceProperties.class)
public class CustomerConfig {
  @Resource
  private JpaVendorAdapter jpaVendorAdapter;
  @Resource
  private CustomerDataSourceProperties properties;

  @Bean(name = "customerDataSource", initMethod = "init", destroyMethod = "close")
  DataSource customerDataSource() throws Exception {
    MysqlXADataSource ds = new MysqlXADataSource();
    ds.setUrl(properties.getJdbcUrl());
    ds.setUser(properties.getUsername());
    ds.setPassword(properties.getPassword());
    ds.setPinGlobalTxToPhysicalConnection(true);
    AtomikosDataSourceBean xaDs = new AtomikosDataSourceBean();
    xaDs.setXaDataSource(ds);
    xaDs.setUniqueResourceName("xa-customer");
    return xaDs;
  }

  @Bean(name = "customerEntityManager")
  @DependsOn("transactionManager")
  LocalContainerEntityManagerFactoryBean customerEntityManager() throws Throwable {
    HashMap<String, Object> props = new HashMap<>();
    props.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
    props.put("javax.persistence.transactionType", "JTA");
    props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setJtaDataSource(customerDataSource());
    emf.setJpaVendorAdapter(jpaVendorAdapter);
    emf.setPackagesToScan("com.pack.domain.customer");
    emf.setPersistenceUnitName("customerPersistenceUnit");
    emf.setJpaPropertyMap(props);
    return emf;
  }
}

Order Data Source

@ConfigurationProperties(prefix = "pack.datasource.order")
public class OrderDataSourceProperties {
  private String jdbcUrl;
  private String username;
  private String password;
}

@Configuration
@DependsOn("transactionManager")
@EnableJpaRepositories(
  basePackages = "com.pack.repository.order",
  entityManagerFactoryRef = "orderEntityManager",
  transactionManagerRef = "transactionManager")
@EnableConfigurationProperties(OrderDataSourceProperties.class)
public class OrderConfig {
  @Resource
  private JpaVendorAdapter jpaVendorAdapter;
  @Resource
  private OrderDataSourceProperties properties;

  @Bean(name = "orderDataSource", initMethod = "init", destroyMethod = "close")
  DataSource orderDataSource() throws Exception {
    MysqlXADataSource ds = new MysqlXADataSource();
    ds.setUrl(properties.getJdbcUrl());
    ds.setUser(properties.getUsername());
    ds.setPassword(properties.getPassword());
    ds.setPinGlobalTxToPhysicalConnection(true);
    AtomikosDataSourceBean xaDs = new AtomikosDataSourceBean();
    xaDs.setXaDataSource(ds);
    xaDs.setUniqueResourceName("xa-order");
    return xaDs;
  }

  @Bean(name = "orderEntityManager")
  @DependsOn("transactionManager")
  LocalContainerEntityManagerFactoryBean orderEntityManager() throws Throwable {
    HashMap<String, Object> props = new HashMap<>();
    props.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
    props.put("javax.persistence.transactionType", "JTA");
    props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setJtaDataSource(orderDataSource());
    emf.setJpaVendorAdapter(jpaVendorAdapter);
    emf.setPackagesToScan("com.pack.domain.order");
    emf.setPersistenceUnitName("orderPersistenceUnit");
    emf.setJpaPropertyMap(props);
    return emf;
  }
}

2.7 Main Configuration

@Configuration
@ComponentScan
@EnableTransactionManagement
public class MainConfig {
  @Bean
  JpaVendorAdapter jpaVendorAdapter() {
    HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
    adapter.setShowSql(true);
    adapter.setGenerateDdl(true);
    adapter.setDatabase(Database.MYSQL);
    return adapter;
  }

  @Bean(name = "userTransaction")
  UserTransaction userTransaction() throws Throwable {
    UserTransactionImp ut = new UserTransactionImp();
    ut.setTransactionTimeout(10000);
    return ut;
  }

  @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
  TransactionManager atomikosTransactionManager() throws Throwable {
    UserTransactionManager utm = new UserTransactionManager();
    utm.setForceShutdown(false);
    AtomikosJtaPlatform.transactionManager = utm;
    return utm;
  }

  @Bean(name = "transactionManager")
  @DependsOn({"userTransaction", "atomikosTransactionManager"})
  PlatformTransactionManager transactionManager() throws Throwable {
    UserTransaction ut = userTransaction();
    AtomikosJtaPlatform.transaction = ut;
    TransactionManager tm = atomikosTransactionManager();
    return new JtaTransactionManager(ut, tm);
  }
}

2.8 Test Service

@Service
public class BaseService {
  @Resource
  private CustomerService customerService;
  @Resource
  private OrderService orderService;

  @Transactional
  public void save(Customer customer, Order order) {
    customerService.save(customer);
    orderService.save(order);
  }

  @Transactional
  public void saveLocalExecption(Customer customer, Order order) {
    customerService.save(customer);
    orderService.save(order);
    throw new RuntimeException("LocalException 发生异常");
  }

  @Transactional
  public void saveCustomerException(Customer customer, Order order) {
    customerService.save(customer);
    orderService.save(order);
  }
}

2.9 Test Cases

@Test
public void testSaveNormal() {
  Customer customer = new Customer();
  customer.setAge(10);
  customer.setName("张三");
  Order order = new Order();
  order.setCode(10001);
  order.setQuantity(10);
  baseService.save(customer, order);
}

@Test
public void testSaveLocalExecption() {
  Customer customer = new Customer();
  customer.setAge(10);
  customer.setName("张三");
  Order order = new Order();
  order.setCode(10001);
  order.setQuantity(10);
  baseService.saveLocalExecption(customer, order);
}

Running the normal test commits both transactions and the data appears in the database. The exception tests cause both transactions to roll back, leaving the database empty. This confirms that Atomikos successfully manages distributed transactions in Spring Boot.

For the complete source, refer to the accompanying PDF e‑book (139 cases) and the image diagrams included above.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentSpring BootDistributed TransactionsAtomikosJTA
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.