Configure Atomikos JTA with Spring Boot for Multi‑DataSource Transactions
This guide walks through setting up a Spring Boot project with JDK 8, Atomikos, and JPA to manage two MySQL data sources, configure JTA transaction handling, define entity managers, services, and a controller, and demonstrates successful and failing transaction scenarios with code snippets and screenshots.
Environment
JDK 8+, Atomikos 2.4.12, Spring Boot with JPA.
Dependencies (pom.xml)
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>Application Configuration (application.yml)
server:
port: 9101
---
spring:
application:
name: atomikos
---
spring:
jta:
enabled: true
datasource:
account:
resourceName: accountResource
dsClassName: com.mysql.cj.jdbc.MysqlXADataSource
url: jdbc:mysql://localhost:3306/account?serverTimezone=GMT+8&useSSL=false
username: root
password: 123123
storage:
resourceName: storageResource
dsClassName: com.mysql.cj.jdbc.MysqlXADataSource
url: jdbc:mysql://localhost:3306/storage?serverTimezone=GMT+8&useSSL=false
username: root
password: 123123
---
spring:
jpa:
generateDdl: false
hibernate:
ddlAuto: update
openInView: true
show-sql: true
properties:
hibernate:
physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
dialect: org.hibernate.dialect.MySQL5Dialect
transaction:
jta:
platform: com.pack.jpa.config.AtomikosJtaPlatform
javax:
persistence:
transactionType: JTA
---
debug: falseIf physical_naming_strategy is not configured, column names may not match the expected naming convention.
Data Source Property Classes
@Data
public class BaseDataSourceProperties {
private String resourceName;
private String dsClassName;
private String driver;
private String url;
private String username;
private String password;
} @Component
@ConfigurationProperties(prefix = "spring.datasource.account")
public class AccountDataSourceProperties extends BaseDataSourceProperties {} @Component
@ConfigurationProperties(prefix = "spring.datasource.storage")
public class StorageDataSourceProperties extends BaseDataSourceProperties {}DataSource Beans
@Configuration
public class DataSourceConfig {
@Bean(name = "accountDataSource", initMethod = "init", destroyMethod = "close")
@Primary
public DataSource accountDataSource(AccountDataSourceProperties props) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName(props.getResourceName());
ds.setXaDataSourceClassName(props.getDsClassName());
Properties xaProperties = new Properties();
xaProperties.setProperty("url", props.getUrl());
xaProperties.setProperty("user", props.getUsername());
xaProperties.setProperty("password", props.getPassword());
ds.setXaProperties(xaProperties);
ds.setMinPoolSize(10);
ds.setMaxPoolSize(10);
ds.setBorrowConnectionTimeout(30);
ds.setMaxLifetime(60);
ds.setMaintenanceInterval(60);
return ds;
}
@Bean(name = "storageDataSource", initMethod = "init", destroyMethod = "close")
public DataSource storageDataSource(StorageDataSourceProperties props) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName(props.getResourceName());
ds.setXaDataSourceClassName(props.getDsClassName());
Properties xaProperties = new Properties();
xaProperties.setProperty("url", props.getUrl());
xaProperties.setProperty("user", props.getUsername());
xaProperties.setProperty("password", props.getPassword());
ds.setXaProperties(xaProperties);
ds.setMinPoolSize(10);
ds.setMaxPoolSize(10);
ds.setBorrowConnectionTimeout(30);
ds.setMaxLifetime(60);
ds.setMaintenanceInterval(60);
return ds;
}
}JPA Entity Manager Configuration
public class EntityManagerFactoryConfig {
@Configuration
@EnableJpaRepositories(
basePackages = {"com.pack.account.repository"},
entityManagerFactoryRef = "accountEntityManagerFactory",
transactionManagerRef = "transactionManager"
)
@DependsOn("transactionManager")
static class AccountEntityManagerFactory {
@Resource(name = "accountDataSource")
private DataSource accountDataSource;
@Resource
private JpaProperties props;
private String accountDomainPkg = "com.pack.account.domain";
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean accountEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(accountDataSource)
.packages(accountDomainPkg)
.persistenceUnit("account")
.properties(props.getProperties())
.build();
}
}
@Configuration
@EnableJpaRepositories(
basePackages = {"com.pack.storage.repository"},
entityManagerFactoryRef = "storageEntityManagerFactory",
transactionManagerRef = "transactionManager"
)
@DependsOn("transactionManager")
static class StorageEntityManagerFactory {
@Resource(name = "storageDataSource")
private DataSource storageDataSource;
@Resource
private JpaProperties props;
private String storageDomainPkg = "com.pack.storage.domain";
@Bean
public LocalContainerEntityManagerFactoryBean storageEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(storageDataSource)
.packages(storageDomainPkg)
.persistenceUnit("storage")
.properties(props.getProperties())
.build();
}
}
}Transaction Management
public class AtomikosJtaPlatform extends AbstractJtaPlatform {
private static final long serialVersionUID = 1L;
public static TransactionManager transactionManager;
public static UserTransaction transaction;
@Override
protected TransactionManager locateTransactionManager() {
return transactionManager;
}
@Override
protected UserTransaction locateUserTransaction() {
return transaction;
}
} @Configuration
public class TxConfig {
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({"userTransaction", "atomikosTransactionManager"})
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}The AtomikosJtaPlatform class is the custom JTA platform referenced in the application.yml configuration.
Entity Classes
@Entity
@Table(name = "t_account")
public class Account {
@Id
private Long id;
private String userId;
private BigDecimal money;
} @Entity
@Table(name = "t_storage")
public class Storage {
@Id
private Long id;
private String commodityCode;
private Integer count;
}Repositories and Services
public interface AccountRepository extends JpaRepository<Account, Long>, JpaSpecificationExecutor<Account> {}
public interface StorageRepository extends JpaRepository<Storage, Long>, JpaSpecificationExecutor<Storage> {} @Service
public class AccountService {
@Resource
private AccountRepository accountRepository;
@Transactional
public void saveAccount(Account account) {
accountRepository.save(account);
}
} @Service
public class StorageService {
@Resource
private StorageRepository storageRepository;
@Transactional
public void saveStorage(Storage storage) {
storageRepository.save(storage);
}
} @Service
public class OperatorService {
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
@Transactional
public void save(Account account, Storage storage) {
accountService.saveAccount(account);
if (storage.getCount() == 0) {
throw new RuntimeException("库存数量错误0");
}
storageService.saveStorage(storage);
if (storage.getCount() == -1) {
throw new RuntimeException("库存数量错误-1");
}
}
}The @Transactional annotation on OperatorService.save ensures that if any exception occurs, the whole transaction is rolled back.
Controller and Test Cases
@RestController
@RequestMapping("/oc")
public class OperatorController {
@Resource
private OperatorService os;
@PostMapping("/save")
public Object save(@RequestBody OperatorDTO dto) {
os.save(dto.getAccount(), dto.getStorage());
return "1";
}
}Successful execution inserts both records; failure (e.g., setting count to 0 or -1) triggers an exception and rolls back the transaction.
When the transaction fails, no data is persisted to the database.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
