欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【SpringBoot2.0】基于Atomikos的多数据源分布式事务(XA)解决方案

程序员文章站 2022-05-23 13:25:59
...

最近工作中在同一项目中用到了多数据源,虽然项目本身对多数据源的事务没有要求,甚至可以不使用事务。但是本着精益求精的原则,加上各种资料的查阅,终于实现了多数据源的XA(分布式事务)

项目框架

  • springboot2.0.x
  • springmvc
  • mybatis

对多数据源的mapper的处理

针对多个数据源,mapper通常有两种处理方式:一种是将各个数据源对应的mapper放置在不同的package中,通过配置各个对应的SqlSessionFactory来实例化各自的mapper;另一种是使用动态数据源,即在程序运行时,通过注解or函数动态的设置当前使用哪个数据源,而mapper本身是不知道自己会使用哪个数据源的

目前我使用的是前一种方案,毕竟这种方案各个mapper都很清楚的知道自己将会使用哪个数据源,第二种方案我目前还没使用的需求。

对于如何配置各个数据源对应不同的mapper,我会再后续的文章中阐述。本文着重阐述如何在springboot中配置XA

第一步,引入spring-boot-starterspring-boot-starter-jta-atomikos

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

springboot中配置xa共需要配置两个东西,一个是支持xa的数据源,一个是基于xa的分布式事务管理器
因为在项目中我们已经引入了spring-boot-starter-jta-atomikos,它会自带一个支持xa的分布式事务管理器(此依赖还会自动创建一个支持xa的数据源,但是只能自动创建一个,而我们如果用xa的话,肯定是多数据源,所以这里感觉springboot的这个“默认”配置有点zz),因此我们只需要配置好多个数据源即可(必须是支持xa的数据源)

我们这里以两个数据源为例:
我们先构造一个数据源创建器

@Component
public class DataSourceConfig implements BeanClassLoaderAware{

    @Setter
    ClassLoader beanClassLoader;

    protected XADataSource createXaDataSource(DataSourceProperties properties) {
        String className = properties.getXa().getDataSourceClassName();
        if (!StringUtils.hasLength(className)) {
            className = DatabaseDriver.fromJdbcUrl(properties.determineUrl())
                    .getXaDataSourceClassName();
        }
        Assert.state(StringUtils.hasLength(className),
                "No XA DataSource class name specified");
        XADataSource dataSource = createXaDataSourceInstance(className);
        bindXaProperties(dataSource, properties);
        return dataSource;
    }

    private XADataSource createXaDataSourceInstance(String className) {
        try {
            Class<?> dataSourceClass = ClassUtils.forName(className, this.beanClassLoader);
            Object instance = BeanUtils.instantiateClass(dataSourceClass);
            Assert.isInstanceOf(XADataSource.class, instance);
            return (XADataSource) instance;
        }
        catch (Exception ex) {
            throw new IllegalStateException(
                    "Unable to create XADataSource instance from '" + className + "'");
        }
    }

    private void bindXaProperties(XADataSource target,
            DataSourceProperties dataSourceProperties) {
        Binder binder = new Binder(getBinderSource(dataSourceProperties));
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
    }

    private ConfigurationPropertySource getBinderSource(
            DataSourceProperties dataSourceProperties) {
        MapConfigurationPropertySource source = new MapConfigurationPropertySource();
        source.put("user", dataSourceProperties.determineUsername());
        source.put("password", dataSourceProperties.determinePassword());
        source.put("url", dataSourceProperties.determineUrl());
        source.putAll(dataSourceProperties.getXa().getProperties());
        ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
        aliases.addAliases("user", "username");
        return source.withAliases(aliases);
    }
}

这里思路是使用特定的配置文件(DataSourceProperties)来构建一个DataSource对象,这里有不少代码都参考的SpringBoot中XADataSourceAutoConfiguration.java的源码
然后我们构建两个数据源:
第一个数据源

    @Configuration
    public static class MybatisDbBConfigODS extends DataSourceConfig{

        @Autowired
        XADataSourceWrapper wrapper;

        @Bean
        @Primary
        @ConfigurationProperties("spring.datasource.ods")
        public DataSourceProperties getDataSourceProperties() {
            return new DataSourceProperties();
        }

        @Bean
        @Primary
        public DataSource getDataSource() throws Exception {
            XADataSource xaDataSource = createXaDataSource(getDataSourceProperties());
            return this.wrapper.wrapDataSource(xaDataSource);
        }

    }

这里的思路是先将当前数据源需要的属性(例如用户名、密码、数据库连接地址等)加载到DataSourceProperties中,然后使用此对象来创建DataSource对象,注意这里的XADataSourceWrapper对象,它的作用是将XA数据源转化为普通的数据源。
第二个数据源的创建方法类似:

    @Configuration
    @MapperScan(basePackages = "com.xinbo.ods.dao.cdr", sqlSessionFactoryRef = "sqlSessionFactory4cdr", sqlSessionTemplateRef = "sqlSessionTemplate4cdr", annotationClass = Mapper.class)
    public static class MybatisDbBConfigCDR  extends DataSourceConfig{

        @Autowired
        XADataSourceWrapper wrapper;

        @Bean("UNJi29SsAF19WVCkeKdt")
        @ConfigurationProperties("spring.datasource.cdr")
        public DataSourceProperties getDataSourceProperties() {
            return new DataSourceProperties();
        }

        @Bean("ahS54La6KNmWnchdQQil")
        public DataSource getDataSource4cdr() throws Exception {
            XADataSource xaDataSource = createXaDataSource(getDataSourceProperties());
            return this.wrapper.wrapDataSource(xaDataSource);
        }
    }

要注意的是,最好每个@Bean注解中使用唯一的名字,否则会和上的重名,然后出现问题
到这里所有的配置已经配置好了,然后我们使用事务时,只需要在需要的类或方法上加上@Transactional注解即可,当一个事务中同时操作多个数据库时,遇到异常会回滚对所有数据库的操作。

PS:对于springboot是如何创建支持XA的事务管理器TransactionManager的,如果有兴趣,可以参考这个类的源码:AtomikosJtaConfiguration.java