Spring Cloud Alibaba Dubbo整合Seata(Fescar)实现分布式事物回滚

前言

作者使用的开发套件是Spring Cloud Alibaba,RPC框架采用Dubbo Spring Cloud,使用Feign的同学也可以看看,原理相通,大同小异。

准备阶段

  1. 配置数据库,创建Seata回滚需要的表UNDO_LOG和业务表
  2. 启动Seata Server

这部分建议阅读 官方文档-README

核心原理

Seata 全局事务的传播机制就是指事务上下文的传播,根本上,就是 XID 的应用运行时的传播方式。默认的,RootContext 的实现是基于 ThreadLocal 的,即 XID 绑定在当前线程上下文中。
而我们在分布式系统中,各系统不在一个线程中,为了使Seata全局事物能够在分布式系统中传播,需要对此进行扩展,Seata内置了Dubbo的支持,引用官方文档中的描述:

对 Dubbo 的支持,我们利用了 Dubbo 框架的 org.apache.dubbo.rpc.Filter 机制。

这部分建议阅读 官网文档-微服务框架支持

开始配置

通过阅读Feign + Seata的配置教程,可以得知我们需要配置的就是将GlobalTransactionScannerDataSourceProxy加入Spring容器
GlobalTransactionScanner的配置较为简单,而配置DataSourceProxy网上主流的方式是手动配置,如:

DruidDataSource dataSource = initDataSource(dataSourceProps.get("url").toString(), dataSourceProps.get("username").toString(), dataSourceProps.get("password").toString());
DataSourceProxy proxy = new DataSourceProxy(dataSource);

这种方式的确简单,却使很多配置项丢失,不是完美的解决方式。完美的解决方式应为在Spring容器初始化后用new DataSourceProxy(dataSource)替换原有dataSource对象。

那么应如何替换呢,我们可以通过实现Spring提供的ApplicationContextAware接口来获取ApplicationContext。而ApplicationContext值有getBean(),而不能移除之前的dataSource。我们先看一张继承关系图:

图片转自 https://www.javadoop.com/post/spring-ioc

虽然ApplicationContext不能直接向下转型为DefaultListableBeanFactory,但我们可以通过ApplicationContext中持有的AutowireCapableBeanFactory向下转型为DefaultListableBeanFactory,从而通过它来获取、删除和注册Bean。

说了这么多,最后贴我的配置类

@ConditionalOnClass(GlobalTransactionScanner.class)
@AutoConfigureBefore(name = "mybatisPlusAutoConfiguration")
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@Configuration
public class SeataAutoConfiguration implements ApplicationContextAware {

    @Value("${spring.application.name}")
    private String applicationName;

    private String groupName = "my_test_tx_group";

    @Bean
    public GlobalTransactionScanner globalTransactionScanner() {
        if (applicationName == null) {
            return new GlobalTransactionScanner(groupName);
        } else {
            return new GlobalTransactionScanner(applicationName, groupName);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取SpringBoot自动配置的DataSource
        DataSource originDataSource = applicationContext.getBean(DataSource.class);
        log.debug("originDataSource = {}", applicationContext.getBean("dataSource"));
        DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        // 从Spring容器中删除原dataSource
        defaultListableBeanFactory.removeBeanDefinition("dataSource");
        // 将原数据源包装成DataSourceProxy
        DataSourceProxy dataSourceProxy = new DataSourceProxy(originDataSource);
        // 向Spring容器中注册DataSourceProxy
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DataSource.class, () -> dataSourceProxy);
        defaultListableBeanFactory.registerBeanDefinition("dataSource", beanDefinitionBuilder.getBeanDefinition());
        log.debug("proxyDataSource = {}", applicationContext.getBean("dataSource"));
    }
}

参考链接:

Seata Wiki
Spring Cloud Alibaba Seata示例项目
Feign + Seata配置教程
Spring IOC 容器源码分析

你可能感兴趣的:(Spring Cloud Alibaba Dubbo整合Seata(Fescar)实现分布式事物回滚)