配置多个数据源的事务

配置多个数据源的事务

一、使用场景

​ 在我们的工作中,肯定会遇到多数据源的情况,如何配置多数据源我前面也有文章写过,这里就不多讨论了。今天学到了一手配置多数据源的事务,就给大家分享分享吧。

​ 其实Spring也有事务注解@Transactional,但是他只能满足一种数据源事务的提交,而我们分布式项目肯定不止步于单数据源,有着多个多种数据源,如果要在b数据源删除一个数据,a数据源增加一个数据的话,程序不出问题还好,一旦报出异常就可能导致a数据源删除数据成功,b数据源增加数据失败的情况,而@Transactional注解是做不到这类事务的回滚的,这个时候就需要配置多数据源的事务了。今天就讲一讲具体的配置步骤,主要思想还是利用Spring的AOP实现。

二、配置步骤

  1. 第一步当然是配置文件配置数据源,application.yml文件如下:

    spring:
      datasource:
        druid:
          pethome:
            url: jdbc:mysql:///pethome?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver
            type: com.alibaba.druid.pool.DruidDataSource
          book:
            url: jdbc:mysql:///book_manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false
            username: root
            password: 123456
            driver-class-name: com.mysql.jdbc.Driver
            type: com.alibaba.druid.pool.DruidDataSource
    
  2. 第二步就是创建一个配置类,然后配置yml文件里面的数据源为Datasource.以及两个数据源的事务管理器。

    @Configuration
    public class DatasourceConfig {
        @Bean(name = "book")
        @Primary
    	@ConfigurationProperties(prefix = "spring.datasource.druid.book")
    	public DataSource book() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(name = "pethome")
    	@ConfigurationProperties(prefix = "spring.datasource.druid.pethome")
    	public DataSource pethome() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean("pethomeManager")
        public DataSourceTransactionManager dataSourceTransactionManager2(@Qualifier("pethome") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
        @Bean("bookManager")
        @Primary
        public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("book") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
  3. 第三步是配置一个多数据源的注解@DatasourceTransactionals

    /**
     * 事务注解
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface DataSourceTransactionals {
     
        /**
         * 事务管理器数组
         */
        String[] transactionManagers();
    }
    
  4. 第四步创建注解的AOP,为多数据源提交回滚事务。

    @Component
    @Aspect
    @Order(-1)
    public class DataSourceTransactionAspect {
     
        /**
         * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※
         */
        private static final ThreadLocal<Stack<Pair<DataSourceTransactionManager, TransactionStatus>>> THREAD_LOCAL = new ThreadLocal<>();
     
        /**
         * 用于获取事务管理器
         */
        @Autowired
        private ApplicationContext applicationContext;
     
        //设置事务定义器
        public DefaultTransactionDefinition getTransactionDefinition(){
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            // 非只读模式
            def.setReadOnly(false);
            // 事务隔离级别:采用数据库的
            def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
            // 事务传播行为
            def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
            return def;
    
        }
     
        /**
         * 切面
         */
        @Pointcut("@annotation(cn.antu.dynamic.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(getTransactionDefinition());
                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();
        }
    }
    
  5. 下面就是测试,这里使用的是jdbctemplate,如果你使用的是mybatis还需要第六步。

    public class TestController {
        @Resource(name = "pethome")
        private DataSource pethome;
    
        @Resource(name = "book")
        private DataSource book;
    //    @Resource(name = "bookTransactionManager")
    //    private DataSourceTransactionManager dataSourceTransactionManager;
    
        @RequestMapping(value="/test1",method= RequestMethod.POST)
        @ApiOperation(value = "测试jdbc", notes = "测试")
        @DataSourceTransactionals(transactionManagers = {"pethomeManager","bookManager"})
        public String test(){
            JdbcTemplate jdbcTemplate1 = new JdbcTemplate(book);
            jdbcTemplate1.update("delete FROM  books WHERE id = 19");
    
            JdbcTemplate jdbcTemplate = new JdbcTemplate(pethome);
            jdbcTemplate.update("delete FROM  t_menu WHERE id = 22");
            jdbcTemplate.update("delete FROM  t_employee WHERE id = 338");
            System.out.println(1/0);
            return "11";
        }
    }
    
  6. mybatis专属配置,非必须

    public class MybatisConfig {
    
        @Configuration
        @MapperScan(basePackages = "cn.antu.dynamic.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
        public static class bookConfig {
    
            @Bean
            @Primary
            public SqlSessionFactory sqlSessionFactory(@Qualifier("book") 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);
            }
    
        }
    
        @Configuration
        @MapperScan(basePackages = "cn.antu.dynamic.mapper", sqlSessionTemplateRef = "sqlSessionTemplate2")
        public static class pethomeConfig {
    
            @Bean
            public SqlSessionFactory sqlSessionFactory2(@Qualifier("pethome") 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);
            }
    
        }
    }
    

三、总结

​ 总的来说还是很简单的,把代码复制到项目中,数据源改一改即可使用。其实最主要的还是切面哪里,首先定义一个DefaultTransactionDefinition类,然后在before方法里面遍历出@DatasourceTrsactionals注解设置的事务管理器数组,利用ApplicationContext得到对应的事务管理器bean,再得到TransactionStatus,然后以对应的事务管理器为key,TransactionStatus为value放到pair中,最后在入栈。遍历完以后放到ThreadLocal中去。接着是afterReturning方法用于提交事务,先在ThreadLocal里面得到当前线程的stack<pair<DataSourceTransactionManager, TransactionStatus>>,然后利用key/value提交事务,至于回滚是在afterThrowing方法里,和提交类似。最后提交或者回滚的时候记得删除当前栈哟(threadlocal.remove())哟。

​ 今天的分享就到这里了,感谢大家的观看。欢迎大家收藏订阅哟!

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值