zoukankan      html  css  js  c++  java
  • MybatisPlus多数据源及事务解决思路

    关于多数据源解决方案

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

    1. 继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,使用AOP切面注入相应的数据源 ,但是这种做法仅仅适用单Service方法使用一个数据源可行,如果单Service方法有多个数据源执行会造成误读。
    2. 通过DataSource配置 JdbcTemplateBean,直接使用 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多数据源的误走,且支持多数据源下的事务问题。

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

    本文首发于我的个人博客左羽(一杯茶)

  • 相关阅读:
    校园商铺-2项目设计和框架搭建-8升级mysql驱动相关的配置以支持mysql8
    校园商铺-2项目设计和框架搭建-7验证Dao
    校园商铺-2项目设计和框架搭建-6逐层完成SSM的各项配置
    校园商铺-2项目设计和框架搭建-5配置maven
    校园商铺-2项目设计和框架搭建-2实体类设计与表创建
    1移动测试流程和技术体系
    校园商铺-2项目设计和框架搭建-1系统功能模块划分
    校园商铺-1开发准备-3 Eclipse与maven的联合配置
    校园商铺-1开发准备-2开发准备
    校园商铺-1开发准备-1课程序章
  • 原文地址:https://www.cnblogs.com/1214804270hacker/p/14299379.html
Copyright © 2011-2022 走看看