zoukankan      html  css  js  c++  java
  • Springboot+Mybatisplus多数据源以及实现事务一致性

    Springboot+Mybatis-plus多数据源以及实现事务一致性

    在实际项目开发中,会同时连接2个或者多个数据库进行开发,因此我们需要配置多数据源,在使用多数据源的时候,在业务中可能会对2个不同的数据库进行插入、修改等操作,如何保证多数据源的事务一致性问题?主要解决如下问题:

    • 如何配置多数据源
    • 如何保证事务一致性

    1.多数据源配置

    如果只是配置多数据可以使用mybatis-plus的注解@DS,@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

    官方文档: https://baomidou.com/pages/a61e1b/#文档-documentation

    image-20211224132010843

    2.事务一致性

    现在有2个数据库,需要同时对2个数据库中的表都进行插入操作,此时如果使用注解@Transactional就不行了。

    通过配置不同的Mapper接口扫描路径使用不同的SqlSessionTemplate来实现。不同的SqlSessionTemplate就是不同的SqlSessionFactory,也就是不同的DataSource。

    2.1添加POM文件

    <!-- MyBatis Plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.2</version>
    </dependency>
    <!-- 多数据源-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.3.2</version>
    </dependency>
    

    2.2 配置2个不同的数据源

    spring:
      datasource:
        dynamic:
          primary: master
          datasource:
            master:
              jdbc-url: jdbc:mysql://xxxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
              username: root
              password: root
            slave:
              jdbc-url: jdbc:mysql://xxx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
              username: root
              password: 123
    

    2.3 创建2个mapper包

    2个mapper包分别对应存放2个数据源对应的mapper文件,这个里面没有什么特殊的,和之前怎么做现在还是怎么做

    image-20211224144854659

    • 创建MasterDataSourceConfig配置文件

      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.core.MybatisConfiguration;
      import com.baomidou.mybatisplus.core.config.GlobalConfig;
      import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
      import com.qz.soft.sampling.config.MybatisPlusConfig;
      import org.apache.ibatis.plugin.Interceptor;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.type.JdbcType;
      import org.mybatis.spring.SqlSessionFactoryBean;
      import org.mybatis.spring.SqlSessionTemplate;
      import org.mybatis.spring.annotation.MapperScan;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.boot.context.properties.ConfigurationProperties;
      import org.springframework.boot.jdbc.DataSourceBuilder;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Primary;
      import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
      import org.springframework.jdbc.datasource.DataSourceTransactionManager;
      import org.springframework.transaction.PlatformTransactionManager;
      
      import javax.annotation.Resource;
      import javax.sql.DataSource;
      
      /**
       * @author sean
       * @date 2021/12/23
       */
      
      @Configuration
      @MapperScan(basePackages = "com.sean.soft.sampling.mapper.master",sqlSessionFactoryRef = "masterSqlSessionFactory")
      public class MasterDataSourceConfig {
          @Resource
          private MybatisPlusConfig mybatisPlusConfig;
      
          @Primary
          @Bean("masterDataSource")
          @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
          public DataSource masterDataSource()
          {
              return DataSourceBuilder.create().build();
          }
          @Primary
          @Bean("masterSqlSessionFactory")
          public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
              //如果要使用mybatis-plus的功能的话需要使用MybatisSqlSessionFactoryBean,不要使用SqlSessionFactoryBean,否则使用mybatis-plus里面的方法会报错找不到该方法
              MybatisSqlSessionFactoryBean  bean = new MybatisSqlSessionFactoryBean();
              
              bean.setDataSource(dataSource);
      
              MybatisConfiguration configuration = new MybatisConfiguration();
              configuration.setJdbcTypeForNull(JdbcType.NULL);
              configuration.setMapUnderscoreToCamelCase(true);
              configuration.setCacheEnabled(false);
              bean.setConfiguration(configuration);
              //添加分页功能
              Interceptor[] plugins = {mybatisPlusConfig.mybatisPlusInterceptor()};
              bean.setPlugins(plugins);
              //设置全局配置
              GlobalConfig globalConfig = new GlobalConfig();
              globalConfig.setDbConfig(new GlobalConfig.DbConfig().setIdType(IdType.ASSIGN_ID));
              globalConfig.setBanner(false);
              bean.setGlobalConfig(globalConfig);
      
              bean.setTypeAliasesPackage("com.qz.soft.sampling.entity");
              bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/master/*.xml"));
              return bean.getObject();
          }
      
          @Primary
          @Bean("masterSqlSessionTemplate")
          public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory")SqlSessionFactory sqlSessionFactory)
          {
              return new SqlSessionTemplate(sqlSessionFactory);
          }
      
          @Primary
          @Bean("masterTransactionManager")
          public PlatformTransactionManager masterTransactionManager(@Qualifier("masterDataSource")DataSource dataSource)
          {
              return new DataSourceTransactionManager(dataSource);
          }
      
      }
      
      
    • 创建SlaveDataSourceConfig配置文件

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.core.MybatisConfiguration;
    import com.baomidou.mybatisplus.core.config.GlobalConfig;
    import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
    import com.qz.soft.sampling.config.MybatisPlusConfig;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.type.JdbcType;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.annotation.Resource;
    import javax.sql.DataSource;
    
    /**
     * @author sean
     * @date 2021/12/23
     */
    
    @Configuration
    @MapperScan(basePackages = "com.sean.soft.sampling.mapper.slave",sqlSessionFactoryRef = "slaveSqlSessionFactory")
    public class SlaveDataSourceConfig {
        @Resource
        private MybatisPlusConfig mybatisPlusConfig;
    
    
        @Bean("slaveDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.slave")
        public DataSource masterDataSource()
        {
            return DataSourceBuilder.create().build();
        }
    
        @Bean("slaveSqlSessionFactory")
        public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
            MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
            bean.setDataSource(dataSource);
    
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setCacheEnabled(false);
            bean.setConfiguration(configuration);
    
            //添加分页功能
            Interceptor[] plugins = {mybatisPlusConfig.mybatisPlusInterceptor()};
            bean.setPlugins(plugins);
            //全局配置
            GlobalConfig globalConfig = new GlobalConfig();
            globalConfig.setDbConfig(new GlobalConfig.DbConfig().setIdType(IdType.ASSIGN_ID));
            globalConfig.setBanner(false);
            bean.setGlobalConfig(globalConfig);
    
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/slave/*.xml"));
            return bean.getObject();
        }
    
    
        @Bean("slaveSqlSessionTemplate")
        public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory")SqlSessionFactory sqlSessionFactory)
        {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    
    
        @Bean("slaveTransactionManager")
        public PlatformTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource")DataSource dataSource)
        {
            return new DataSourceTransactionManager(dataSource);
        }
    
    }
    

    2.4 创建自定义注解@CustomTransaction

    /**
     * @author sean
     * @date 2021/12/23
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER,ElementType.METHOD})
    public @interface CustomTransaction {
        String[] value() default {};
    
    }
    

    2.5 创建AOP切面,解析自定义注解

    import cn.hutool.core.util.ArrayUtil;
    import com.qz.soft.sampling.util.BeanUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    
    import java.util.Stack;
    
    /**
     * @author sean
     * @date 2021/12/23
     */
    @Slf4j
    @Aspect
    @Configuration
    public class TransactionAop {
    
        @Pointcut("@annotation(com.qz.soft.sampling.annotation.CustomTransaction)")
        public void CustomTransaction() {
        }
    
        @Around(value = "CustomTransaction() && @annotation(annotation)")
        public Object syncLims(ProceedingJoinPoint joinPoint, CustomTransaction annotation) throws Throwable {
            Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
            Stack<TransactionStatus> transactionStatusStack = new Stack<>();
            try {
                if (!openTransaction(dataSourceTransactionManagerStack, transactionStatusStack, annotation)) {
                    return null;
                }
                Object ret = joinPoint.proceed();
                commit(dataSourceTransactionManagerStack,transactionStatusStack);
                return ret;
            }catch (Throwable e)
            {
                rollback(dataSourceTransactionManagerStack,transactionStatusStack);
                log.error(String.format("MultTransactionAspect, method:%s-%s occors error:",joinPoint.getTarget().getClass().getSimpleName(),
                        joinPoint.getSignature().getName()),e);
                throw e;
            }
        }
    
        /**
         * 开启事务处理方法
         *
         * @param dataSourceTransactionManagerStack
         * @param transactionStatusStack
         * @param multiTransaction
         * @return
         */
        public Boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                                       Stack<TransactionStatus> transactionStatusStack, CustomTransaction multiTransaction) {
    
    
            String[] transactionManagerNames = multiTransaction.value();
            if (ArrayUtil.isEmpty(transactionManagerNames)) {
                return false;
            }
    
            for (String beanName : transactionManagerNames) {
                DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) BeanUtil.getBean(beanName);
                TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
                transactionStatusStack.push(transactionStatus);
                dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
            }
            return true;
        }
    
        /**
         * 提交处理方法
         *
         * @param dataSourceTransactionManagerStack
         * @param transactionStatusStack
         */
        private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                            Stack<TransactionStatus> transactionStatusStack) {
            while (!dataSourceTransactionManagerStack.isEmpty()) {
                dataSourceTransactionManagerStack.pop().commit(transactionStatusStack.pop());
            }
        }
    
        /**
         * 回滚处理方法
         * @param dataSourceTransactionManagerStack
         * @param transactionStatusStack
         */
        private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                              Stack<TransactionStatus> transactionStatusStack) {
            while (!dataSourceTransactionManagerStack.isEmpty()) {
                dataSourceTransactionManagerStack.pop().rollback(transactionStatusStack.pop());
            }
        }
    
    
    }
    
    

    这样我们就完成了整个代码的编写,下面就进行测试,测试的时候只需要在方法上使用自定义注解@CustomTransaction(value = {"masterTransactionManager","slaveTransactionManager"})

    image-20211226200323876

    参考文档:

    https://www.cnblogs.com/red-star/p/12535919.html

    https://blog.csdn.net/qq_31142553/article/details/102768696

  • 相关阅读:
    『数学』--数论--组合数+卢卡斯定理+扩展卢卡斯定理
    Lucene高亮
    Linux 计划任务
    Lucene.net(4.8.0) 学习问题记录二: 分词器Analyzer中的TokenStream和AttributeSource
    Asp.net Core 异步调用 Task await async 的梳理
    Asp.net core 中的依赖注入
    Lucene.net(4.8.0) 学习问题记录一:分词器Analyzer的构造和内部成员ReuseStategy
    Git 使用篇二:小组协作开发
    Git 使用篇二:搭建远程服务器
    Git 使用篇一:初步使用GitHub,下载安装git,并上传项目
  • 原文地址:https://www.cnblogs.com/seanRay/p/15735882.html
Copyright © 2011-2022 走看看