组件整合之使用Atomikos实现跨库实现分布式事务

背景

在Java后端开发过程中事务控制非常重要,而Spring为我们提供了方便的声明式事务方法@Transactional。但是默认的Spring事务只支持单数据源,而实际上一个系统往往需要写多个数据源,这个时候我们就需要考虑如何通过Spring实现对分布式事务的支持。

对于数据库层面的分布式事务而言,JTA(Java Transaction API,XA的JAVA实现方案)是一个不错的解决方案,通常JTA需要应用服务器的支持。 而Atomikos 是一个为Java平台提供增值服务的并且开源类的事务管理器,主要用于处理跨数据库事务,比如某个指令在A库和B库都有写操作,业务上要求A库和B库的写操作要具有原子性,这时候就可以用到atomikos。

Atomikos分布式事务

Atomikos公司旗下有两款著名的分布事务产品:

  • TransactionEssentials:开源的免费产品
  • ExtremeTransactions:商业版,需要收费

这两个产品的关系如下图所示:

可以看到,在开源版本中支持JTA/XA、JDBC、JMS的分布式事务。

最简单的情况下,你只需要引入如下依赖:

<dependency>    
    <groupId>com.atomikos</groupId>
    <artifactId>transactions-jdbc</artifactId>    
    <version>4.0.6</version>
</dependency>

atomikos也支持与spring事务整合。spring事务管理器的顶级抽象是PlatformTransactionManager接口,其提供了个重要的实现类:

  • DataSourceTransactionManager:用于实现本地事务
  • JTATransactionManager:用于实现分布式事务

显然,在这里,我们需要配置的是JTATransactionManager。

下面的代码片段了,演示了与spring事务整合后的分布式事务的案例代码。假设有两个mybatis映射器接口UserMapper和AccountMapper,操作不同的库。

public class JTAService {   

    //操作db_user库  
    @Autowired   
    private UserMapper userMapper;
    
    //操作db_account库 
    @Autowired   
    private AccountMapper accountMapper;  
    
    @Transactional   
    public void insert() {      
        User user = new User();      
        user.setName("wangxiaoxiao");      
        userMapper.insert(user);      
        //模拟异常,spring回滚后,db_user库中user表中也不会插入记录      
        Account account = new Account();      
        account.setUserId(user.getId());     
        account.setMoney(123456789);      
        accountMapper.insert(account);   
    }
}

可以发现分布式事务的逻辑,与操作单库事务基本上是完全相同的,底层的复杂逻辑对应用程序开发者来说完全屏蔽。

快速入门

1、依赖

<!--spring-tx-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>5.0.2.RELEASE</version>
</dependency>

<!-- jta -->
<dependency>
  <groupId>javax.transaction</groupId>
  <artifactId>jta</artifactId>
  <version>1.1</version>
</dependency>

<!--atomikos-->
<dependency>
  <groupId>com.atomikos</groupId>
  <artifactId>transactions-jdbc</artifactId>
  <version>4.0.6</version>
</dependency>

2、数据源配置

application.properties

jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username=root
jdbc.password=root
jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc2.url=jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc2.username=root
jdbc2.password=root
jdbc2.driverClassName=com.mysql.jdbc.Driver

AppPropertiesConfig.java

@Data
@Configuration
@PropertySource("classpath:application.properties")
public class AppPropertiesConfig {

    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
    @Value("${jdbc.driverClassName}")
    private String driverClassName;
}

AppPropertiesConfig2.java

@Data
@Configuration
@PropertySource("classpath:application.properties")
public class AppPropertiesConfig2 {

    @Value("${jdbc2.url}")
    private String jdbcUrl;
    @Value("${jdbc2.username}")
    private String userName;
    @Value("${jdbc2.password}")
    private String password;
    @Value("${jdbc2.driverClassName}")
    private String driverClassName;

}
@Bean("springBuiltDataSource")
public AtomikosDataSourceBean springBuiltDataSource(AppPropertiesConfig propertiesConfig) throws SQLException {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUrl(propertiesConfig.getJdbcUrl());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    mysqlXaDataSource.setPassword(propertiesConfig.getPassword());
    mysqlXaDataSource.setUser(propertiesConfig.getUserName());
    // 将本地事务注册到创 Atomikos全局事务
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXaDataSource);
    xaDataSource.setUniqueResourceName("dataSourceOne");

    xaDataSource.setMinPoolSize(10);
    xaDataSource.setMaxPoolSize(1000);
    xaDataSource.setMaxLifetime(20000);
    xaDataSource.setBorrowConnectionTimeout(30);
    xaDataSource.setLoginTimeout(30);
    xaDataSource.setMaintenanceInterval(60);
    xaDataSource.setMaxIdleTime(60);
    xaDataSource.setTestQuery("select now()");
    return xaDataSource;
}

@Bean("springBuiltDataSource2")
public AtomikosDataSourceBean springBuiltDataSource2(AppPropertiesConfig2 propertiesConfig2) throws SQLException {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUrl(propertiesConfig2.getJdbcUrl());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    mysqlXaDataSource.setPassword(propertiesConfig2.getPassword());
    mysqlXaDataSource.setUser(propertiesConfig2.getUserName());
    // 将本地事务注册到创 Atomikos全局事务
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXaDataSource);
    xaDataSource.setUniqueResourceName("dataSourceTwo");

    xaDataSource.setMinPoolSize(10);
    xaDataSource.setMaxPoolSize(1000);
    xaDataSource.setMaxLifetime(20000);
    xaDataSource.setBorrowConnectionTimeout(30);
    xaDataSource.setLoginTimeout(30);
    xaDataSource.setMaintenanceInterval(60);
    xaDataSource.setMaxIdleTime(60);
    xaDataSource.setTestQuery("select now()");
    return xaDataSource;
}

@Bean("jdbcTemplateOne")
public JdbcTemplate jdbcTemplateOne(DataSource springBuiltDataSource){
    return new JdbcTemplate(springBuiltDataSource);
}

@Bean("jdbcTemplateTwo")
public JdbcTemplate jdbcTemplateTwo(DataSource springBuiltDataSource2){
    return new JdbcTemplate(springBuiltDataSource2);
}

3、JtaTransactionManager事务管理器

@Bean("transactionManager")
public PlatformTransactionManager transactionManager() throws SystemException {
    UserTransactionManager userTransactionManager = new UserTransactionManager();
    userTransactionManager.setForceShutdown(true);

    UserTransactionImp atomikosUserTransaction = new UserTransactionImp();
    atomikosUserTransaction.setTransactionTimeout(300);
    return new JtaTransactionManager(atomikosUserTransaction, userTransactionManager);
}

4、测试

@Resource(name = "jdbcTemplateOne")
private JdbcTemplate jdbcTemplateOne;

@Resource(name = "jdbcTemplateTwo")
private JdbcTemplate jdbcTemplateTwo;

@Transactional
public void testJta(){
    jdbcTemplateOne.update("insert into user_test(id, name) values(100, '老板')");
    int a = 1/0;
    jdbcTemplateTwo.update("insert into user_test(id, name) values(100, '老板')");
}

 

posted @ 2022-01-02 22:49  残城碎梦  阅读(276)  评论(0编辑  收藏  举报