springboot2.0 配置多数据源保证事务一致性

如果不去百度,你能说出事务这俩字什么意思吗?

事务是什么

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元

事务说白了,就像那啥啥交易,一首交钱一手”交货“,你爽了,钱也没了,这玩意能光明正大的搞吗,是一起进去,还是一起都好,这就看执行的时候有没有异常了

在说说spring的事务

其实spring的事务也还是数据库层面的的事务,只是把事物管理的代码交给了spring,完事的时候,也是由spring把事物的相关命令提交到数据库的

多数据源事务

重头戏来了,多数据源事务的方法很多,我这里就只叙述切面的方式实现,其他的自行百度,不多说直接上代码

springboot 的版本2.2.x

1 application.yml中添加数据源

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
  datasource1:
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
      driver-class-name: com.mysql.cj.jdbc.Driver

2 事务管理器组

/**
 * 事务注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSourceTransactionals {

    /**
     * 事务管理器数组
     */
    String[] transactionManagers();
}

3 切面类

/**
 * 数据源切面类
 * @author xxxx
 *
 */
@Component
@Aspect
public class DataSourceTransactionAspect {

    /**
     * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※
     */
    private static final ThreadLocal<Stack<Pair<DataSourceTransactionManager, TransactionStatus>>> THREAD_LOCAL = new ThreadLocal<>();

    /**
     * 用于获取事务管理器
     */
    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 事务声明
     */
    private DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    {
        // 非只读模式
        def.setReadOnly(false);
        // 事务隔离级别:采用数据库的
        def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        // 事务传播行为
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    }

    /**
     * 切面
     */
    @Pointcut("@annotation(cn.zhh.annotation.DataSourceTransactionals)")
    public void pointcut() {
    }

    /**
     * 声明事务
     *
     * @param transactional 注解
     */
    @Before("pointcut() && @annotation(transactional)")
    public void before(DataSourceTransactionals transactional) {
        // 根据设置的事务名称按顺序声明,并放到ThreadLocal里
        String[] transactionManagerNames = transactional.transactionManagers();
        Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = new Stack<>();
        for (String transactionManagerName : transactionManagerNames) {
            DataSourceTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class);
            TransactionStatus transactionStatus = transactionManager.getTransaction(def);
            pairStack.push(new Pair(transactionManager, transactionStatus));
        }
        THREAD_LOCAL.set(pairStack);
    }

    /**
     * 提交事务
     */
    @AfterReturning("pointcut()")
    public void afterReturning() {
        // ※栈顶弹出(后进先出)
        Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get();
        while (!pairStack.empty()) {
            Pair<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop();
            pair.getKey().commit(pair.getValue());
        }
        THREAD_LOCAL.remove();
    }

    /**
     * 回滚事务
     */
    @AfterThrowing(value = "pointcut()")
    public void afterThrowing() {
        // ※栈顶弹出(后进先出)
        Stack<Pair<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get();
        while (!pairStack.empty()) {
            Pair<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop();
            pair.getKey().rollback(pair.getValue());
        }
        THREAD_LOCAL.remove();
    }
}

4 数据源配置

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 数据源配置
 *
 */
@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    public DataSource dataSource(@Value("${spring.datasource.username}") String username,
                                 @Value("${spring.datasource.password}") String password,
                                 @Value("${spring.datasource.url}") String url,
                                 @Value("${spring.datasource.driver-class-name}") String driverClassName) {
        return createDataSource(username, password, url, driverClassName);
    }

    @Bean
    public DataSource dataSource2(@Value("${spring.datasource1.username}") String username,
                                  @Value("${spring.datasource1.password}") String password,
                                  @Value("${spring.datasource1.url}") String url,
                                  @Value("${spring.datasource1.driver-class-name}") String driverClassName) {
        return createDataSource(username, password, url, driverClassName);
    }

    private DataSource createDataSource(String username, String password, String url, String driverClassName) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClassName(driverClassName);
        return dataSource;
    }
}

5 mybatis的配置类

/**
 * Mybatis配置类
 *
 */
public class MybatisConfig {

    @Configuration
    @MapperScan(basePackages = "扫描的业务mapper的路径", sqlSessionTemplateRef = "sqlSessionTemplate")
    public static class Db1 {

        @Bean
        @Primary
        public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource);
            return factoryBean.getObject();
        }

        @Bean
        @Primary
        public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }

        @Bean
        @Primary
        public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }

    @Configuration
    @MapperScan(basePackages = "扫描的业务mapper的路径1", sqlSessionTemplateRef = "sqlSessionTemplate2")
    public static class Db2 {

        @Bean
        public SqlSessionFactory sqlSessionFactory2(@Qualifier("dataSource1") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource);
            return factoryBean.getObject();
        }

        @Bean
        public SqlSessionTemplate sqlSessionTemplate2(@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }

        @Bean
        public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("dataSource2") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
}

执行结果

1 如果一个方法中对两个数据库有操作时:

  • 添加事务注解,异常在两个执行语句的前面
  • 添加事务注解,异常在两个执行语句的中间
  • 添加事务注解,异常在两个执行语句的后面

这三种情况两个表全部回滚,如下

2 只添加第一个事务(异常都在两个执行语句的后面)

user表有值

3 只添加第二个事务(异常都在两个执行语句的后面)

student表中也有值了,user表中的2还是上次提交产生的数据

所以说,这种方式的多数据源事务是生效的

这有个问题,在异常后,表主键id不变化,然而在后面正常提交时id会自动累加,我查了资料说是mysql的底层机制,不管了功能好使就行

其他的逻辑就按照自己的业务逻辑补充即可 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值