SpringBoot多數據源切換及多數據源事務控制實現過程(附成功案例)

一、概述

項目採用spring-boot 2.1.6,mybatis,並採用了mybatis-plus中間件,數據庫爲mysql5.6。

二、實現過程

2.1 實現多數據源

根據網上的大部分人的分享,我大概理清了思路,大致是這樣:

1.寫一個Holder通過改變ThreadLocal來直接修改數據源的名稱
2.寫一個註解,再在aop裏配置一下,需要切換時通過註解來完成

這兩種實現其實都是大同小異的,知道其實現原理纔是最重要的。
第一步、創建數據源上下文分配對象:DataSourceContextHolder

package com.daodianlai.commons.jdbc;

import lombok.extern.slf4j.Slf4j;

/**
 * DataSourceContextHolder
 *
 * @author yangqisheng
 * @date 2019/08/08
 */
@Slf4j
public class DataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 設置數據庫
     *
     * @param dataBase 數據庫名稱
     */
    public static void setDataBase(String dataBase) {
        log.debug("切換到{" + dataBase + "}數據庫");
        contextHolder.set(dataBase);
    }

    /**
     * 獲取當前數據庫名稱
     *
     * @return 數據庫名稱
     */
    public static String getDataBase() {
        return (contextHolder.get());
    }

    /**
     * 清除已設置的數據庫
     */
    public static void clearDataData() {
        log.debug("清除{" + contextHolder.get() + "}數據庫");
        contextHolder.remove();
    }
}

第二步、實現路由切換AbstractRoutingDataSource

package com.daodianlai.commons.jdbc;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 動態數據源路由切換
 *
 * @author yangqisheng
 * @date 2019/08/08
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataBase();
    }
}

下一步是創建多數據源,因爲採用了Mybatis-Plus所以將數據源配置放在了MybatisPlusConfig裏。

package com.daodianlai.commons.config;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.daodianlai.commons.jdbc.DynamicDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * MybatisPlusConfig
 *
 * @author yangqisheng
 * @date 2019/07/17
 */
@Configuration
@MapperScan(basePackages = {
        "com.daodianlai.*.dao.mapper",
        "com.daodianlai.*.*.dao.mapper"
})
public class MybatisPlusConfig {

    /**
     * 分頁插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    /**
     * 獲取默認到店來數據源
     *
     * @return 到店來數據源
     */
    @Bean(name = "defaultDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSource defaultDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 獲取aswpx數據源
     *
     * @return 微評選數據源
     */
    @Bean(name = "aswpxDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.aswpx")
    public DataSource aswpxDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 獲取hhd數據源
     *
     * @return hhd數據源
     */
    @Bean(name = "hhdDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.hhd")
    public DataSource hhdDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 動態數據源: 通過AOP在不同數據源之間動態切換
     *
     * @return 動態數據源
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(
            @Qualifier("defaultDataSource") DataSource defaultDataSource,
            @Qualifier("aswpxDataSource") DataSource aswpxDataSource,
            @Qualifier("hhdDataSource") DataSource hhdDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默認數據源
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
        // 配置多數據源
        Map<Object, Object> dataSourceMap = new HashMap();
        dataSourceMap.put("defaultDataSource", defaultDataSource());
        dataSourceMap.put("aswpxDataSource", aswpxDataSource);
        dataSourceMap.put("hhdDataSource", hhdDataSource);
        //設置數據源Map
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dynamicDataSource(defaultDataSource(), aswpxDataSource(), hhdDataSource()));

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        //添加插件
        sqlSessionFactory.setPlugins(new Interceptor[]{
                paginationInterceptor()
        });
        return sqlSessionFactory.getObject();
    }
}

最後一步就是定義攔截器,切換數據源

1.默認數據源 DefultDataSourceAspect
2.aswpx數據源 AswpxDataSourceAspect
3. hhd數據源 HhdDataSourceAspect

下面給一下具體實現:

package com.daodianlai.commons.jdbc;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * DefaultDataSourceAspect
 *
 * @author yangqisheng
 * @date 2019/08/08
 */
@Aspect
@Order(-1)
@Component
public class AswpxDataSourceAspect {

    @Pointcut("execution(* com.daodianlai.marketing.dao.mapper.*Mapper.*(..))")
    public void defaultAspect() {
    }

    @Before("defaultAspect()")
    public void before(JoinPoint defaultAspect) {
        // 切換數據源
        DataSourceContextHolder.setDataSource("defaultDataSource");
    }

    @After("aswpxAspect()")
    public void after(JoinPoint defaultAspect) {
        // 清除數據源
        DataSourceContextHolder.clearDataSource();
    }
}
package com.daodianlai.commons.jdbc;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * AswpxDataSourceAspect
 *
 * @author yangqisheng
 * @date 2019/08/08
 */
@Aspect
@Order(-1)
@Component
public class AswpxDataSourceAspect {

    @Pointcut("execution(* com.daodianlai.aswpx.dao.mapper.*Mapper.*(..))")
    public void aswpxAspect() {
    }

    @Before("aswpxAspect()")
    public void before(JoinPoint aswpxAspect) {
        // 切換數據源
        DataSourceContextHolder.setDataSource("aswpxDataSource");
    }

    @After("aswpxAspect()")
    public void after(JoinPoint aswpxAspect) {
        // 清除數據源
        DataSourceContextHolder.clearDataSource();
    }
}
package com.daodianlai.commons.jdbc;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * HhdDataSourceAspect
 *
 * @author yangqisheng
 * @date 2019/08/08
 */
@Aspect
@Order(-1)
@Component
public class HhdDataSourceAspect {

    @Pointcut("execution(* com.daodianlai.zpi.hhd.dao.mapper.*Mapper.*(..))")
    public void hhdAspect() {
    }

    @Before("hhdAspect()")
    public void before(JoinPoint hhdAspect) {
        // 切換數據源
        DataSourceContextHolder.setDataSource("hhdDataSource");
    }

    @After("hhdAspect()")
    public void after(JoinPoint hhdAspect) {
        // 清除數據源
        DataSourceContextHolder.clearDataSource();
    }
}

至此已經完成了多數據源的配置,下面給出數據源配置:

spring:
  application:
    name: daodianlai-marketing
  aop:
    proxy-target-class: true
    auto: true
  datasource:
    default:
      jdbc-url: jdbc:mysql://localhost:3306/dapdianlai?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    aswpx:
      jdbc-url: jdbc:mysql://localhost:3306/aswpx?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
    hhd:
      jdbc-url: jdbc:mysql://localhost:3306/hhd?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: longsu
      driver-class-name: com.mysql.cj.jdbc.Driver

“重點解讀一下jdbc-url”
正常我們設置數據源的時候用的都是url,這裏爲什麼用的是jdbc-url呢?我是在這篇文章裏找到的答案,有興趣可以去看一下。

jdbcUrl is required with driverClassName錯誤解決
https://blog.csdn.net/newbie_907486852/article/details/81391525

好了,我做了系統測試,運行正常,那麼多數據源這事做完了嗎?
答案是“沒有”!
當我在service的方法上加了一個事務註解後,報錯了,代碼如下

@Transactional(rollbackFor = {Exception.class})
public void updateData() {
    aswpxMapper.excute("update t_user set num = num + 1");
    daodianlaiMapper.excute("update t_visit set num = num + 1");
}

系統報了以下錯誤:

### Cause: java.sql.SQLSyntaxErrorException: Table 'daodianlai.t_visit' doesn't exist

很明顯,是數據源沒切回來,那麼什麼原因呢?
這篇文章講得很詳細,感興趣的可以移步去看看

spring項目中多數據源無法切換的原因分析
https://blog.csdn.net/puhaiyang/article/details/88013712

所以,怎麼才能實現切換動態數據源還能控制事務呢?
我在這篇文章裏找到了方案,並自己成功實現了。

springboot+mybatis解決多數據源切換事務控制不生效的問題
https://blog.csdn.net/gaoshili001/article/details/79378902

下面我給出最終的實現代碼,多數據源,分佈式事務代碼。
採用的是jpa+atomikos方案

根據Spring官方介紹,要想自己控制事務,則必須實現Transaction接口,所以我們來創建動態數據源事務實現類DynamicDataSourceTransaction

DynamicDataSourceTransaction.java

package com.daodianlai.commons.jdbc;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.jdbc.CannotGetJdbcConnectionException;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.util.Assert;

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

/**
 * 動態數據源事務
 *
 * @author yangqisheng
 * @date 2019/08/09
 */
@Slf4j
public class DynamicDataSourceTransaction implements Transaction {

    private final DataSource dataSource;
    private Connection defaultConnection;
    private String dataBaseName;
    private ConcurrentMap<String, Connection> dynamicConnectionMap;
    private boolean isConnectionTransactional;
    private boolean autoCommit;

    public DynamicDataSourceTransaction(DataSource dataSource) {
        Assert.notNull(dataSource, "No DataSource specified");
        this.dataSource = dataSource;
        this.dynamicConnectionMap = new ConcurrentHashMap<>();
        this.dataBaseName = DataSourceContextHolder.getDataBase();
    }

    @Override
    public Connection getConnection() throws SQLException {
        String dataBase = DataSourceContextHolder.getDataBase();
        if (dataBase.equals(dataBaseName)) {
            if (defaultConnection != null) {
                return defaultConnection;
            }
            openMainConnection();
            dataBaseName = dataBase;
            return defaultConnection;
        } else {
            if (!dynamicConnectionMap.containsKey(dataBase)) {
                try {
                    Connection conn = dataSource.getConnection();
                    dynamicConnectionMap.put(dataBase, conn);
                } catch (SQLException ex) {
                    throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
                }
            }
            return dynamicConnectionMap.get(dataBase);
        }
    }

    private void openMainConnection() throws SQLException {
        this.defaultConnection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.defaultConnection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.defaultConnection, this.dataSource);

        if (log.isDebugEnabled()) {
            log.debug(
                    "JDBC Connection ["
                            + this.defaultConnection
                            + "] will"
                            + (this.isConnectionTransactional ? " " : " not ")
                            + "be managed by Spring");
        }
    }

    @Override
    public void commit() throws SQLException {
        if (this.defaultConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + this.defaultConnection + "]");
            }
            this.defaultConnection.commit();
            for (Connection connection : dynamicConnectionMap.values()) {
                connection.commit();
            }
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (this.defaultConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + this.defaultConnection + "]");
            }
            this.defaultConnection.rollback();
            for (Connection connection : dynamicConnectionMap.values()) {
                connection.rollback();
            }
        }
    }

    @Override
    public void close() {
        DataSourceUtils.releaseConnection(this.defaultConnection, this.dataSource);
        for (Connection connection : dynamicConnectionMap.values()) {
            DataSourceUtils.releaseConnection(connection, this.dataSource);
        }
    }

    @Override
    public Integer getTimeout() {
        return null;
    }
}

重寫動態數據源事務管理工廠
DynamicDataSourceTransactionFactory.java

package com.daodianlai.commons.jdbc;

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

import javax.sql.DataSource;

/**
 * 重寫動態數據源事務管理工廠
 *
 * @author yangqisheng
 * @date 2019/08/09
 */
public class DynamicDataSourceTransactionFactory extends SpringManagedTransactionFactory {

    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new DynamicDataSourceTransaction(dataSource);
    }
}

當然最後一步,我們完全的重寫了MybatisPlusConfig
新MybatisPlusConfig.java

package com.daodianlai.commons.config;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.daodianlai.commons.jdbc.DynamicDataSource;
import com.daodianlai.commons.jdbc.DynamicDataSourceTransactionFactory;
import com.mysql.cj.jdbc.MysqlXADataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

/**
 * MybatisPlusConfig
 *
 * @author yangqisheng
 * @date 2019/07/17
 */
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = {
        "com.daodianlai.*.dao.mapper",
        "com.daodianlai.*.*.dao.mapper"
})
public class MybatisPlusConfig {

    /**
     * 分頁插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    @Primary
    @Bean(name = "defaultDataSourceProperties")
    @Qualifier("defaultDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSourceProperties defaultDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 獲取默認到店來數據源
     *
     * @return 到店來數據源
     */
    @Bean(name = "defaultDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSource defaultDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setUrl(defaultDataSourceProperties().getUrl());
        mysqlXaDataSource.setUser(defaultDataSourceProperties().getUsername());
        mysqlXaDataSource.setPassword(defaultDataSourceProperties().getPassword());

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("default");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    @Bean(name = "aswpxDataSourceProperties")
    @Qualifier("aswpxDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.aswpx")
    public DataSourceProperties aswpxDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 獲取微評選數據源
     *
     * @return 微評選數據源
     */
    @Bean(name = "aswpxDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.aswpx")
    public DataSource aswpxDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setUrl(aswpxDataSourceProperties().getUrl());
        mysqlXaDataSource.setUser(aswpxDataSourceProperties().getUsername());
        mysqlXaDataSource.setPassword(aswpxDataSourceProperties().getPassword());

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("aswpx");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    @Bean(name = "hhdDataSourceProperties")
    @Qualifier("hhdDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.hhd")
    public DataSourceProperties hhdDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 獲取慧惠多數據源
     *
     * @return 慧惠多數據源
     */
    @Bean(name = "hhdDataSource", initMethod = "init", destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.hhd")
    public DataSource hhdDataSource() throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setUrl(hhdDataSourceProperties().getUrl());
        mysqlXaDataSource.setUser(hhdDataSourceProperties().getUsername());
        mysqlXaDataSource.setPassword(hhdDataSourceProperties().getPassword());

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("hhd");
        xaDataSource.setBorrowConnectionTimeout(60);
        xaDataSource.setMaxPoolSize(20);
        return xaDataSource;
    }

    /**
     * 動態數據源: 通過AOP在不同數據源之間動態切換
     *
     * @return 動態數據源
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource(
            @Qualifier("defaultDataSource") DataSource defaultDataSource,
            @Qualifier("aswpxDataSource") DataSource aswpxDataSource,
            @Qualifier("hhdDataSource") DataSource hhdDataSource) throws SQLException {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默認數據源
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
        // 配置多數據源
        Map<Object, Object> dataSourceMap = new HashMap();
        dataSourceMap.put("defaultDataSource", defaultDataSource());
        dataSourceMap.put("aswpxDataSource", aswpxDataSource);
        dataSourceMap.put("hhdDataSource", hhdDataSource);
        //設置數據源Map
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        //設置動態數據源
        sqlSessionFactory.setDataSource(dynamicDataSource(defaultDataSource(), aswpxDataSource(), hhdDataSource()));
        //設置動態數據源事務管理
        sqlSessionFactory.setTransactionFactory(new DynamicDataSourceTransactionFactory());

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        //添加插件
        sqlSessionFactory.setPlugins(new Interceptor[]{
                paginationInterceptor()
        });
        return sqlSessionFactory.getObject();
    }

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

    @Bean(name = "userTransactionManager", initMethod = "init", destroyMethod = "close")
    public TransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(true);
        return userTransactionManager;
    }

    // 默認事務管理器
    @Bean(name = "transactionManager")
    @DependsOn({"userTransaction", "userTransactionManager"})
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        TransactionManager userTransactionManager = userTransactionManager();
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(userTransaction, userTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }
}

最後做了測試,完美切換數據源,並實現事務回滾。

package com.daodianlai.task;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
 * MoreTranTask
 *
 * @author yangqisheng
 * @date 2019/08/09
 */
@Slf4j
@Component
public class MoreTranTask {

    @Autowired
    private MoreTranService moreTranService;

    @Scheduled(cron = "0 * * * * ?")
    public void run() {
        log.info(">>>>【多事務測試】開始");
        moreTranService.updateData();
        log.info(">>>>【多事務測試】結束");
    }
}
package com.daodianlai.task;

import com.daodianlai.aswpx.dao.mapper.AswpxMapper;
import com.daodianlai.commons.jdbc.DataSourceContextHolder;
import com.daodianlai.zpi.sdcbd.dao.mapper.DaodianlaiMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * MoreTranService
 *
 * @author yangqisheng
 * @date 2019/08/09
 */
@Slf4j
@Service
public class MoreTranService {

    @Autowired
    private AswpxMapper aswpxMapper;
    @Autowired
    private DaodianlaiMapper daodianlaiMapper;

    @Transactional(
            transactionManager = "transactionManager",
            propagation = Propagation.REQUIRED, readOnly = false,
            rollbackFor = {Exception.class})
    public void updateData() {
        aswpxMapper.excute("update t_user set num = num + 1");
        daodianlaiMapper.excute("update t_visit set num = num + 1");
        //此處會報錯,引發事務回滾
        int a = 1 / 0;
    }
}

希望大家調試順利~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章