转载

MybatisPlus多数据源及事务解决思路

关于多数据源解决方案

目前在 SpringBoot 框架基础上多数据源的解决方案大多手动创建多个 DataSource ,后续方案有三:

  1. 继承 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource ,使用 AOP 切面注入相应的数据源 ,但是这种做法仅仅适用单 Service 方法使用一个数据源可行,如果单 Service 方法有多个数据源执行会造成误读。
  2. 通过 DataSource 配置 JdbcTemplate Bean,直接使用 JdbcTemplate 操控数据源。
  3. 分别通过 DataSource 创建 SqlSessionFactory 并扫描相应的 Mapper 文件和 Mapper 接口。

MybatisPlus多数据源及事务解决思路

MybatisPlus 的多数据源

我通过阅读源码,发现 MybatisPlus 的多数据源解决方案正是 AOP ,继承了 org.springframework.jdbc.datasource.AbstractDataSource ,有自己对 ThreadLocal 的处理。通过注解切换数据源。也就是说, MybatisPlus 只支持在单 Service 方法内操作一个数据源,毕竟官网都指明—— “强烈建议只注解在service实现上”

而后,注意看 com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder ,也就是 MybatisPlus 是如何切换数据源的。

重点看:

/**
   * 为什么要用链表存储(准确的是栈)
   * <pre>
   * 为了支持嵌套切换,如ABC三个service都是不同的数据源
   * 其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
   * 传统的只设置当前线程的方式不能满足此业务需求,必须模拟栈,后进先出。
   * </pre>
   */
  private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
    @Override
    protected Object initialValue() {
      return new ArrayDeque();
    }
  };

这段话翻译为大家都能懂得的意思就是**“可以同时操控多个数据源”**。那么,在 MYSQL 中,有语法为 schemaName + . + tableName ,如此一来就不会误走数据源了。

我继续看 MybatisPlus 是如何利用 mybatis 本身的 ORM 机制将实体类自动映射以及生成 SQL 语句的(这里插一句, MybatisPlus 的源码易读懂,写的很不错)。无意看到了注解 com.baomidou.mybatisplus.annotation.TableName 中的 schema ,如果在类上加 schema ,在生成 SQL 语句时就会生成 schemaName + . + tableName 格式。

MybatisPlus 多数据源事务( JTA

简单说明一下 JTA

JTA 包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。

JTA 只是提供了一个接口,并没有提供具体的实现。

不过 Atomikos 对其进行了实现,而后 SpringBoot 将其进行了整合,对其进行了托管,很方便开发者拿来即用。

其中事务管理器的主要部分为 UserTransaction 接口,开发人员通过此接口在信息系统中实现分布式事务;而资源管理器则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。

通常接入 JTA 步骤(目的就是让 JTAUserTransaction 接管驱动为分布式的数据源,通常为 AtomikosDataSourceBean ):

  1. 配置好 AtomikosDataSourceBean
  2. AtomikosDataSourceBean 交给 SqlSessionFactory
  3. 配置 UserTransaction 事务管理。

但是我们用的是 MybatisPlus ,我们需要做的是接管 MybatisPlus 每一个数据源的配置,然后再把数据源依次交给 MybatisPlus 进行管理。

看看 MybatisPlus 是怎么进行多数据源配置的,源码里有这几个地方需要重点看一下:

  1. com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider ,这个就是 MybatisPlus 多数据源配置的方式,利用 HashMap 来装载。
  2. com.baomidou.dynamic.datasource.DynamicDataSourceCreator ,这个是每个数据源的配置方式。

其中 com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider 实现了接口 com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider ,是该接口的默认的实现。也就是说我们只需要实现该接口,自己配置多数据源以及每个数据源的驱动,成为该接口的默认实现就OK。

  • 实现该接口,配置多数据源:

    package xxx.xxx.xxx.config;
    
    import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
    import org.springframework.context.annotation.Primary;
    import org.springframework.stereotype.Service;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author : zuoyu
     * @description : 接管MybatisPlus多数据源至Atomikos管理
     * @date : 2020-06-01 16:36
     **/
    @Service
    @Primary
    public class DynamicDataSourceProviderImpl implements DynamicDataSourceProvider {
    
    
        /**
         * 配置文件数据的松散绑定
         */
        private final DynamicDataSourceProperties properties;
    
        /**
         * Atomikos驱动数据源创建
         */
        private final AtomikosDataSourceCreator atomikosDataSourceCreator;
    
        public DynamicDataSourceProviderImpl(DynamicDataSourceProperties properties, AtomikosDataSourceCreator atomikosDataSourceCreator) {
            this.properties = properties;
            this.atomikosDataSourceCreator = atomikosDataSourceCreator;
        }
    
        @Override
        public Map<String, DataSource> loadDataSources() {
            Map<String, DataSourceProperty> dataSourcePropertiesMap = properties.getDatasource();
            Map<String, DataSource> dataSourceMap = new HashMap<>(dataSourcePropertiesMap.size() * 2);
            for (Map.Entry<String, DataSourceProperty> item : dataSourcePropertiesMap.entrySet()) {
                String pollName = item.getKey();
                DataSourceProperty dataSourceProperty = item.getValue();
                dataSourceProperty.setPollName(pollName);
                dataSourceMap.put(pollName, atomikosDataSourceCreator.createDataSource(dataSourceProperty));
            }
            return dataSourceMap;
        }
    }
  • Atomikos 驱动数据源创建:

    package xxx.xxx.xxx.config;
    
    import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
    import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
    import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
    import org.springframework.stereotype.Component;
    
    import javax.sql.DataSource;
    
    /**
     * @author : zuoyu
     * @description : 事务数据源
     * @date : 2020-06-01 17:30
     **/
    @Component
    public class AtomikosDataSourceCreator {
        /**
         * 创建数据源
         *
         * @param dataSourceProperty 数据源信息
         * @return 数据源
         */
        public DataSource createDataSource(DataSourceProperty dataSourceProperty) {
            MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
            mysqlXaDataSource.setUrl(dataSourceProperty.getUrl());
            mysqlXaDataSource.setPassword(dataSourceProperty.getPassword());
            mysqlXaDataSource.setUser(dataSourceProperty.getUsername());
            AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
            xaDataSource.setXaDataSource(mysqlXaDataSource);
            xaDataSource.setMinPoolSize(5);
            xaDataSource.setBorrowConnectionTimeout(60);
            xaDataSource.setMaxPoolSize(20);
            xaDataSource.setXaDataSourceClassName(dataSourceProperty.getDriverClassName());
            xaDataSource.setTestQuery("SELECT 1 FROM DUAL");
            xaDataSource.setUniqueResourceName(dataSourceProperty.getPollName());
            return xaDataSource;
        }
    }
  • 配置 JTA 事务管理器:

    package xxx.xxx.xxx.config;
    
    import com.atomikos.icatch.jta.UserTransactionImp;
    import com.atomikos.icatch.jta.UserTransactionManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    import org.springframework.transaction.jta.JtaTransactionManager;
    
    import javax.transaction.TransactionManager;
    import javax.transaction.UserTransaction;
    
    /**
     * @author : zuoyu
     * @description : 分布式事务配置
     * @date : 2020-06-01 17:55
     **/
    @Configuration
    @EnableTransactionManagement
    public class TransactionManagerConfig {
    
        @Bean(name = "userTransaction")
        public UserTransaction userTransaction() throws Throwable {
            UserTransactionImp userTransactionImp = new UserTransactionImp();
            userTransactionImp.setTransactionTimeout(10000);
            return userTransactionImp;
        }
    
        @Bean(name = "atomikosTransactionManager")
        public TransactionManager atomikosTransactionManager() throws Throwable {
            UserTransactionManager userTransactionManager = new UserTransactionManager();
            userTransactionManager.setForceShutdown(false);
            return userTransactionManager;
        }
    
        @Bean(name = "transactionManager")
        @DependsOn({"userTransaction", "atomikosTransactionManager"})
        public PlatformTransactionManager transactionManager() throws Throwable {
            return new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
        }
    }

如此,即可

这样一来便可解决 MybatisPlus 多数据源的误走,且支持多数据源下的事务问题。

做任何事情,重要的是思路,而不是搬砖。

原文  https://www.zuoyu.top/archives/mybatisplus多数据源及事务解决思路
正文到此结束
Loading...