SpringBoot2.X版本之Mybatis(AbstractRoutingDataSource)多数据源切换+atomikos支持分布式事务

前言:

SpringBoot2.X版本的Mybatis多数据源动态切换的配置可以参照我上一篇文章:SpringBoot2.X版本之Mybatis多数据源动态切换_hanxiaozhang的博客-CSDN博客

相关知识介绍:

配置:

1.引入atomikos的架包:

        
            org.springframework.boot
            spring-boot-starter-jta-atomikos
        

2.修改YAML文件: 

将druid的Type:com.alibaba.druid.pool.DruidDataSource修改成com.alibaba.druid.pool.xa.DruidXADataSource,原因:DruidXADataSource支持分布式事务

datasource:
  master:
    type: com.alibaba.druid.pool.xa.DruidXADataSource

jta相关参数配置:

  jta:
    log-dir: classpath:tx-logs
    transaction-manager-id: txManager

3.修改DynamicDataSourceConfig.Java文件:

添加JtaTransactionManager配置:

 /**
     * JtaTransactionManager(分布式事务使用)
     * @return
     * @throws SystemException
     */
    @Bean
    public JtaTransactionManager jtaTransactionManager() throws SystemException {
        log.info("Initialized -> [{}]", "JtaTransactionManager Start");
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setTransactionManager(userTransactionManager());
        jtaTransactionManager.setUserTransaction(userTransactionImp());
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }

    private UserTransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    private UserTransactionImp userTransactionImp() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(5000);
        return userTransactionImp;
    }

修改数据源配置: 

  @Bean
    @Primary
    @ConfigurationProperties(prefix = "datasource.master")
    public DruidXADataSource dbMasterXa() {
        return new DruidXADataSource();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.slave1")
    public DruidXADataSource dbSlave1Xa() {
        return new DruidXADataSource();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.slave2")
    public DruidXADataSource dbSlave2Xa() {
        return new DruidXADataSource();
    }

    @Bean
    @ConfigurationProperties(prefix = "datasource.other")
    public DruidXADataSource dbOtherXa() {
        return new DruidXADataSource();
    }

    @Bean
    public DataSource dbMaster() {
        log.info("Initialized -> [{}]", "DataSource DB_Master Start");
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName(DataSourceKey.DB_MASTER.toString());
        xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        xaDataSource.setXaDataSource(dbMasterXa());
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }
    @Bean
    public DataSource dbSlave1() {
        log.info("Initialized -> [{}]", "DataSource DB_Slave1 Start");
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName(DataSourceKey.DB_SLAVE1.toString());
        xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        xaDataSource.setXaDataSource(dbSlave1Xa());
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }
    @Bean
    public DataSource dbSlave2() {
        log.info("Initialized -> [{}]", "DataSource DB_Slave2 Start");
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName(DataSourceKey.DB_SLAVE2.toString());
        xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        xaDataSource.setXaDataSource(dbSlave2Xa());
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }

    @Bean
    public DataSource dbOther() {
        log.info("Initialized -> [{}]", "DataSource DB_Other Start");
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setUniqueResourceName(DataSourceKey.DB_OTHER.toString());
        xaDataSource.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        xaDataSource.setXaDataSource(dbOtherXa());
        xaDataSource.setPoolSize(5);
        return xaDataSource;
    }

修改sqlSessionFactory配置,添加事务配置:

 @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        log.info("Initialized -> [{}]", "SqlSessionFactory Start");
         ...  ...
         ...  ...
        //配置事务
        sqlSessionFactoryBean.setTransactionFactory(new DynamicDataSourceTransactionFactory());

        return sqlSessionFactoryBean.getObject();

    }

4.重写Transaction支持动态数据源事务(DynamicDataSourceTransaction.java):

package com.hanxiaozhang.config.datasource;

import com.alibaba.druid.support.logging.Log;
import com.alibaba.druid.support.logging.LogFactory;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static org.apache.commons.lang3.Validate.notNull;

/**
 * 〈一句话功能简述〉
* 〈动态数据源的Transaction〉 * * @author hanxinghua * @create 2019/12/26 * @since 1.0.0 */ public class DynamicDataSourceTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(DynamicDataSourceTransaction.class); /** * 数据源 */ private final DataSource dataSource; /** *数据库标识 */ private DataSourceKey mainDataSourceKey; /** * 数据库连接 */ private Connection mainConnection; /** * 标识连接Map */ private ConcurrentMap otherConnectionMap; /** * 是否断开事务 */ private boolean isConnectionTransactional; /** * 是否自动提交 */ private boolean autoCommit; public DynamicDataSourceTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; otherConnectionMap = new ConcurrentHashMap<>(); mainDataSourceKey=DynamicDataSourceContextHolder.get()==null?DataSourceKey.DB_MASTER:DynamicDataSourceContextHolder.get(); } /** * {@inheritDoc} */ @Override public Connection getConnection() throws SQLException { DataSourceKey dataSourceKey = DynamicDataSourceContextHolder.get()==null?DataSourceKey.DB_MASTER:DynamicDataSourceContextHolder.get(); if (dataSourceKey.equals(mainDataSourceKey)) { if (mainConnection != null) { return mainConnection; } else { openMainConnection(); mainDataSourceKey =dataSourceKey; return mainConnection; } } else { if (!otherConnectionMap.containsKey(dataSourceKey)) { try { Connection conn = dataSource.getConnection(); otherConnectionMap.put(dataSourceKey, conn); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } } return otherConnectionMap.get(dataSourceKey); } } private void openMainConnection() throws SQLException { this.mainConnection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.mainConnection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.mainConnection, this.dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "JDBC Connection [" + this.mainConnection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } } /** * {@inheritDoc} */ @Override public void commit() throws SQLException { if (this.mainConnection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Committing JDBC Connection [" + this.mainConnection + "]"); } this.mainConnection.commit(); for (Connection connection : otherConnectionMap.values()) { connection.commit(); } } } /** * {@inheritDoc} */ @Override public void rollback() throws SQLException { if (this.mainConnection != null && !this.isConnectionTransactional && !this.autoCommit) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Rolling back JDBC Connection [" + this.mainConnection + "]"); } this.mainConnection.rollback(); for (Connection connection : otherConnectionMap.values()) { connection.rollback(); } } } /** * {@inheritDoc} */ @Override public void close() throws SQLException { DataSourceUtils.releaseConnection(this.mainConnection, this.dataSource); for (Connection connection : otherConnectionMap.values()) { DataSourceUtils.releaseConnection(connection, this.dataSource); } } @Override public Integer getTimeout() throws SQLException { return null; } }

5.重写TransactionFactory支持动态数据源事务(DynamicDataSourceTransactionFactory.java):

package com.hanxiaozhang.config.datasource;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;

import javax.sql.DataSource;

/**
 * 〈一句话功能简述〉
* 〈动态数据源的TransactionFactory〉 * * @author hanxinghua * @create 2019/12/26 * @since 1.0.0 */ public class DynamicDataSourceTransactionFactory extends SpringManagedTransactionFactory { @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new DynamicDataSourceTransaction(dataSource); } }

6.测试:

在浏览器输入:http://127.0.0.1/test,执行测试。distributedTrans方法中会抛出java.lang.ArithmeticException: / by zero异常,使事务回滚,分别查看两个数据库可以发现数据没有入库,回滚成功

Controller :

    @RequestMapping()
    public String  test1(){
        DictDO dictDO = new DictDO();
        dictDO.setName("分布式事务测试");
        dictDO.setType("分布式事务测试");
        dictDO.setDescription("分布式事务测试");
        dictDO.setCreateDate(new Date());
        testService.distributedTrans(dictDO);
        return "OK";
    }

Service:

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void distributedTrans(DictDO dictDO) {

        testService1.save1(dictDO);

        testService1.save2(dictDO);

        int i=1/0;

    }

    @Override
    @TargetDataSource(dataSourceKey = DataSourceKey.DB_SLAVE1)
    public  void save1(DictDO dictDO){
        dictDao.save(dictDO);
    }


    @Override
    @TargetDataSource(dataSourceKey = DataSourceKey.DB_MASTER)
    public  void save2(DictDO dictDO){
        dictDao.save(dictDO);
    }

7.最后: 

注意mysql-connector-java版本:5.1.46,高版本会遇到一些其他问题,我没有做过多的研究。

源码可以通过邮箱获取:[email protected]

你可能感兴趣的:(#Springboot,Mybatis,Spring,java)