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;
    }
}

希望大家调试顺利~

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