springBoot学习整合Atomikos多数据源的分布式事务

概述:

Atomikos是开源的分布式事务管理器,是JTA规范的实现,支持XA协议。

XA:XA协议由Tuxedo首先提出的,并交给X/Open组织,作为资源管理器(数据库)与事务管理器的接口标准。

目前,Oracle、Informix、DB2和Sybase等各大数据库厂家都提供对XA的支持。XA协议采用两阶段提交方式来管理分布式事务。XA接口提供资源管理器与事务管理器之间进行通信的标准接口。XA协议包括两套函数,以xa_开头的及以ax_开头的。

一. Atomikos依赖

在pom.xml添加atomikos依赖。

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

二. 配置多数据源

# 主数据源
mysql.datasource.one.url = jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
mysql.datasource.one.username =root
mysql.datasource.one.password =123456
mysql.datasource.one.minPoolSize =3
mysql.datasource.one.maxPoolSize =25
mysql.datasource.one.maxLifetime =20000
mysql.datasource.one.borrowConnectionTimeout =30
mysql.datasource.one.loginTimeout =30
mysql.datasource.one.maintenanceInterval =60
mysql.datasource.one.maxIdleTime =60
mysql.datasource.one.testQuery =select 1
# 数据源 2
mysql.datasource.two.url =jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
mysql.datasource.two.username =root
mysql.datasource.two.password =123456
mysql.datasource.two.minPoolSize =3
mysql.datasource.two.maxPoolSize =25
mysql.datasource.two.maxLifetime =20000
mysql.datasource.two.borrowConnectionTimeout =30
mysql.datasource.two.loginTimeout =30
mysql.datasource.two.maintenanceInterval =60
mysql.datasource.two.maxIdleTime =60
mysql.datasource.two.testQuery =select 1

三. 加载配置类

3.1 定义了两个数据源,定义主数据源配置类,加载参数。 // 省略getter 和 setter 方法

@ConfigurationProperties(prefix = “mysql.datasource.one”)
  public class OneDbProperties {
    private String url;
    private String username;
    private String password;
    /** min-pool-size 最小连接数 **/
    private int minPoolSize;
    /** max-pool-size 最大连接数 **/
    private int maxPoolSize;
    /** max-lifetime 连接最大存活时间 **/
    private int maxLifetime;
    /** borrow-connection-timeout 获取连接失败重新获等待最大时间,在这个时间内如果有可用连接,将返回 **/
    private int borrowConnectionTimeout;
    /** login-timeout java数据库连接池,最大可等待获取datasouce的时间 **/
    private int loginTimeout;
    /** maintenance-interval 连接回收时间 **/
    private int maintenanceInterval;
    /** max-idle-time 最大闲置时间,超过最小连接池连接的连接将将关闭 **/
    private int maxIdleTime;
    /** test-query 测试SQL **/
    private String testQuery;

  }

3.2 定义其他数据源配置类,加载参数。// 省略getter 和 setter 方法

@ConfigurationProperties(prefix = “mysql.datasource.two”)
  public class TwoDbProperties {
    private String url;
    private String username;
    private String password;
    /** min-pool-size 最小连接数 **/
    private int minPoolSize;
    /** max-pool-size 最大连接数 **/
    private int maxPoolSize;
    /** max-lifetime 连接最大存活时间 **/
    private int maxLifetime;
    /** borrow-connection-timeout 获取连接失败重新获等待最大时间,在这个时间内如果有可用连接,将返回 **/
    private int borrowConnectionTimeout;
    /** login-timeout java数据库连接池,最大可等待获取datasouce的时间 **/
    private int loginTimeout;
    /** maintenance-interval 连接回收时间 **/
    private int maintenanceInterval;
    /** max-idle-time 最大闲置时间,超过最小连接池连接的连接将将关闭 **/
    private int maxIdleTime;
    /** test-query 测试SQL **/
    private String testQuery;
  }

四. 数据源加载

4.1 主数据源加载, 注入Atomikos的数据源Bean

@Configuration
@MapperScan(basePackages = “com.wenqy.mapper.one”, sqlSessionTemplateRef = “oneSqlSessionTemplate”)
public class OneDbConfig {
  /**
   * @throws SQLException
   * @Title: oneDataSource
   * @Description: 主数据源
   * @param @param oneDbProperties
   * @return DataSource    数据源
   * @throws
   */
  @Primary
  @Bean(name = “oneDataSource”)
  public DataSource oneDataSource(OneDbProperties oneDbProperties) throws SQLException {
    MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
    mysqlXADataSource.setUrl(oneDbProperties.getUrl());
    mysqlXADataSource.setUser(oneDbProperties.getUsername());
    mysqlXADataSource.setPassword(oneDbProperties.getPassword());
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXADataSource);
    xaDataSource.setUniqueResourceName(“oneDataSource”);
    xaDataSource.setMinPoolSize(oneDbProperties.getMinPoolSize());
    xaDataSource.setMaxPoolSize(oneDbProperties.getMaxPoolSize());
    xaDataSource.setMaxLifetime(oneDbProperties.getMaxLifetime());
    xaDataSource.setBorrowConnectionTimeout(oneDbProperties.getBorrowConnectionTimeout());
    xaDataSource.setLoginTimeout(oneDbProperties.getLoginTimeout());
    xaDataSource.setMaintenanceInterval(oneDbProperties.getMaintenanceInterval());
    xaDataSource.setMaxIdleTime(oneDbProperties.getMaxIdleTime());
    xaDataSource.setTestQuery(oneDbProperties.getTestQuery());
    return xaDataSource;
  }
  @Bean(name = “oneSqlSessionFactory”)
  public SqlSessionFactory oneSqlSessionFactory(@Qualifier(“oneDataSource”) DataSource dataSource)
          throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources(“classpath:/mapper/config/one/*.xml”));
            return bean.getObject();
        }
        @Bean(name = “oneSqlSessionTemplate”)
        public SqlSessionTemplate oneSqlSessionTemplate(
                @Qualifier(“oneSqlSessionFactory”) SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

4.2 其他数据源加载,注入Atomikos的数据源Bean

@Configuration
@MapperScan(basePackages = “com.wenqy.mapper.two”, sqlSessionTemplateRef = “twoSqlSessionTemplate”)
public class TwoDbConfig {
  @Bean(name = “twoDataSource”)
  public DataSource twoDataSource(TwoDbProperties twoDbProperties) throws SQLException {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUrl(twoDbProperties.getUrl());
    mysqlXaDataSource.setPassword(twoDbProperties.getPassword());
    mysqlXaDataSource.setUser(twoDbProperties.getUsername());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXaDataSource);
    xaDataSource.setUniqueResourceName(“twoDataSource”);
    xaDataSource.setMinPoolSize(twoDbProperties.getMinPoolSize());
    xaDataSource.setMaxPoolSize(twoDbProperties.getMaxPoolSize());
    xaDataSource.setMaxLifetime(twoDbProperties.getMaxLifetime());
    xaDataSource.setBorrowConnectionTimeout(twoDbProperties.getBorrowConnectionTimeout());
    xaDataSource.setLoginTimeout(twoDbProperties.getLoginTimeout());
    xaDataSource.setMaintenanceInterval(twoDbProperties.getMaintenanceInterval());
    xaDataSource.setMaxIdleTime(twoDbProperties.getMaxIdleTime());
    xaDataSource.setTestQuery(twoDbProperties.getTestQuery());
    return xaDataSource;
  }
  @Bean(name = “twoSqlSessionFactory”)
  public SqlSessionFactory twoSqlSessionFactory(@Qualifier(“twoDataSource”) DataSource dataSource)
          throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources(“classpath:/mapper/config/two/*.xml”));
            return bean.getObject();
        }
        @Bean(name = “twoSqlSessionTemplate”)
        public SqlSessionTemplate twoSqlSessionTemplate(
                @Qualifier(“twoSqlSessionFactory”) SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

五. 事务配置

注入Atomikos自己的事务管理器

/**
 *
 * @ClassName: TransactionManagerConfig
 * @Description: 事务管理配置
 * @author mynah
 *
 */
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig {
  /**
   * 自定义事务 
   * MyBatis自动参与到spring事务管理中,无需额外配置,
   * 只要org.mybatis.spring.SqlSessionFactoryBean
   * 引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
   */
  @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();
    JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager());
    return manager;
  }
}

 

六. 样例测试

6.1 编写测试类,测试服务。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class TestMutiDataSource {
  @Autowired
  private UserService userService;
  @Test
  public void testInsertData() throws Exception {
    userService.insertData();
  }
}

6.2 Service实现类,注释了要抛出异常错误

@Transactional(value="transactionManager")
public void insertData() throws Exception {
    User user = new User();
    user.setId(new Random().nextInt());
    user.setName("mynah");
    userMapper.saveUser( user );
    //      if (true) {
    //          throw new RuntimeException("insert_failure1");
    //      }
    Person person = new Person();
    person.setId(new Random().nextInt());
    person.setEmail("xu@qq.com");
    personMapper.savePerson( person );
    //      if (true) {
    //          throw new RuntimeException("insert_failure2");
    //      }
}

6.3 查看数据库执行结果,是否预期: 结果两表都插入数据

 

6.4 去掉第1个注释块,抛出运行时异常,执行测试,两表没插入数据,正确。

恢复第1个注释块,去掉第2个注释块,抛出运行时异常,执行测试,两表没插入数据,正确。

更换异常为检查异常,如,SQLException时,发现,两表都插入数据了,没有发生回滚,错误。

@Transactional(value="transactionManager")
  public void insertData() throws Exception {
    User user = new User();
    user.setId(new Random().nextInt());
    user.setName("mynah");
    userMapper.saveUser( user );
//      if (true) {
//          throw new RuntimeException("insert_failure1");
//      }
    Person person = new Person();
    person.setId(new Random().nextInt());
    person.setEmail("xu@qq.com");
    personMapper.savePerson( person );
    if (true) {
      throw new SQLException("insert_failure2");
    }
  }

6.5 指定事务需要回滚所抛出的异常,这里是Exception,rollbackFor=Exception.class

继续测试,执行测试,两表没插入数据,说明事务回滚正确。

默认配置下,Spring只有在抛出运行时异常,即RuntimeException及其子类(Errors也会导致事务回滚)时,才回滚该事务。

Atomikos分布式事务和Spring声明式事务一样,对业务透明,非侵入式的。Atomikos支持与Spring的无缝衔接。

而Spring默认的事务隔离级别是数据库默认事务。需要数据库支持事务,如,MySQL不能使用MYISAM引擎。

*** 这个和分布式TCC, Saga 一样的需要执行异常回滚。

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值