• 多数据源系统接入mybatis-plus, 实现动态数据源、动态事务。


    目录:

    • 实现思想
    • 导入依赖、配置说明
    • 代码实现
    • 问题总结

    一.实现思想

      接手一个旧系统,SpringBoot 使用的是纯粹的 mybatis ,既没有使用规范的代码生成器,也没有使用 JPA 或者 mybatis-plus

      想着接入 mybatis-plus,为以后敲代码省点力气。普通的接入 mybatis-plus 可以直接参考官方文档 https://mp.baomidou.com/ 

      但我接手的系统是个多数据源系统,本来最优的方法是使用官方的 动态数据源 支持 https://mp.baomidou.com/guide/dynamic-datasource.html 

      但我因为乱七八糟的依赖冲突,决定自己实现 动态数据源 的支持。

      实现的核心逻辑:使用一个 代理数据源,来管理 其他数据源 的分发请求。(通过AOP分发)

    二.导入依赖、配置说明

      因为依赖的冲突,我没有直接使用

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1.tmp</version>
    </dependency>

      而是引入的

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>3.3.1.tmp</version>
    </dependency>

      数据库配置文件大致如下

    spring:
      datasource: #数据库配置
        primary: #数据库1
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://
          username: root
          password: 3
          type: com.alibaba.druid.pool.DruidDataSource
        second: #数据库2
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://
          username: root
          password: 3
          type: com.alibaba.druid.pool.DruidDataSource
        third: #数据库3
          driver-class-name: com.mysql.cj.jdbc.Driver
          jdbc-url: jdbc:mysql://
          username: root
          password: 3
          type: com.alibaba.druid.pool.DruidDataSource
        

      MybatisPlus 配置大致如下(mybatis 的配置可以删除)

    mybatis-plus:
      # 扫描 mapper.xml
      mapper-locations: classpath:mapper/*.xml #也可以不配置,在代码中设置
    #  configuration:
    #    map-underscore-to-camel-case: false

    三.代码实现

      1.我们先新建 数据源的枚举

    public enum DataSourceEnums {
    
        PRIMARY("primaryDataSource"),
        SECOND("secondDataSource"),
        THIRD("thirdDataSource");
    
        private String value;
    
        DataSourceEnums(String value){this.value=value;}
    
        public String getValue() {
            return value;
        }
    
    }

      2.用来标记数据源的 注解(在哪里使用哪个数据源)。

    /**
     * @author zhaww
     * @date 2020/4/14
     * @Description .自定义 - 区分数据源的注解
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface MyDataSource {
        DataSourceEnums value() default DataSourceEnums.PRIMARY;
    }

      3.动态数据源管理器,继承 AbstractRoutingDataSource  

    /**
     * @author zhaww
     * @date 2020/4/10
     * @Description .动态数据源管理器
     */public class DataSourceContextHolder extends AbstractRoutingDataSource {
    
        private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();
    
        /**
         * 重写这个方法,这里返回使用的数据源 key 值
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
    //        log.info("动态切换数据源:" + DataSourceContextHolder.getDataSource());
            return contextHolder.get();
        }
    
        /**
         *  设置数据源
         * @param db
         */
        public static void setDataSource(String db){
            contextHolder.set(db);
        }
    
        /**
         * 取得当前数据源
         * @return
         */
        public static String getDataSource(){
            return contextHolder.get();
        }
    
        /**
         * 清除上下文数据
         */
        public static void clear(){
            contextHolder.remove();
        }
    
    }

      4. mybatis-plus 的配置类

    /**
     * @author zhaww
     * @date 2020/4/10
     * @Description .
     */
    //@EnableTransactionManagement //开启事务
    @Configuration
    @MapperScan(value = {"com.zydd.admin.dao"}) //扫描Mapper 层的类
    public class MybatisPlusConfig {
    
        @Bean(name = "primaryDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.primary")
        public DataSource primaryDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "secondDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.second")
        public DataSource secondDataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean(name = "thirdDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.third")
        public DataSource thirdDataSource() {
            return DataSourceBuilder.create().build();
        }
    
    
        @Bean(name = "multipleTransactionManager")
        @Primary
        public DataSourceTransactionManager multipleTransactionManager(@Qualifier("multipleDataSource") DataSource dataSource) {
    //        return new MyDataSourceTransactionManager(dataSource);
            return new DataSourceTransactionManager(dataSource);
        }
    
        /**
         * 动态数据源配置
         *
         * @return
         */
        @Bean(name = "multipleDataSource")
        @Primary
        public DataSource multipleDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource,
                                             @Qualifier("secondDataSource") DataSource secondDataSource,
                                             @Qualifier("thirdDataSource") DataSource thirdDataSource) {
            DataSourceContextHolder dynamicDataSource = new DataSourceContextHolder();
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DataSourceEnums.PRIMARY.getValue(), primaryDataSource);
            targetDataSources.put(DataSourceEnums.SECOND.getValue(), secondDataSource);
            targetDataSources.put(DataSourceEnums.THIRD.getValue(), thirdDataSource);
            dynamicDataSource.setTargetDataSources(targetDataSources);
            dynamicDataSource.setDefaultTargetDataSource(thirdDataSource); // 默认使用的数据源
            return dynamicDataSource;
        }
    
        @Bean("sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(multipleDataSource(primaryDataSource(), secondDataSource(), thirdDataSource()));
    
          //mybatis-plus yml 配置不生效,要在这里代码里配置 MybatisConfiguration configuration
    = new MybatisConfiguration(); configuration.setJdbcTypeForNull(JdbcType.NULL); //是否使用转驼峰 configuration.setMapUnderscoreToCamelCase(false); configuration.setCacheEnabled(false); sqlSessionFactory.setConfiguration(configuration); //添加分页功能 Interceptor[] plugins = {paginationInterceptor()}; sqlSessionFactory.setPlugins(plugins); //扫描 mapper 路径 ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource[] resource = resolver.getResources("classpath:mapper/**/*.xml"); sqlSessionFactory.setMapperLocations(resource); return sqlSessionFactory.getObject(); } /** * @Description : mybatis-plus分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false paginationInterceptor.setOverflow(true); // 设置最大单页限制数量,默认 500 条,-1 不受限制 // paginationInterceptor.setLimit(30); return paginationInterceptor; } }

      5.使用AOP来实现数据源的动态设置。

    /**
     * @author zhaww
     * @date 2020/4/9
     * @Description .AOP通用日志记录、动态数据源分发
     */
    @Aspect
    @Component
    @Slf4j
    @Order(-100)
    public class AOP {
    
    
        /**
         * Controller层路径
         */
        @Pointcut("within(com.zydd.admin.controller..*)")
        public void controllerPointcut() {
        }
    
        /**
         * Service层路径
         */
        @Pointcut("within(com.zydd.admin.service..*)")
        public void servicePointcut() {
        }
    
    
        @Around("servicePointcut()")
        public Object doServiceLogging(ProceedingJoinPoint joinPoint) throws Throwable {
            changeDataSource(joinPoint); //检查数据源
        }
    
        /**
         * Mapper层拦截,动态切换 mybatisPlus 数据源
         */
        @Before("execution(* com.zydd.admin.dao.primary..*(..))")
        public void doAdmin(){
            log.info("选择数据源---" + DataSourceEnums.PRIMARY.getValue());
            DataSourceContextHolder.setDataSource(DataSourceEnums.PRIMARY.getValue());
        }
    
        /**
         * Mapper层拦截,动态切换 mybatisPlus 数据源
         */
        @Before("execution(* com.zydd.admin.dao.second..*(..))")
        public void doZYDD(){
            log.info("选择数据源---" + DataSourceEnums.SECOND.getValue());
            DataSourceContextHolder.setDataSource(DataSourceEnums.SECOND.getValue());
        }
    
        /**
         * Mapper层拦截,动态切换 mybatisPlus 数据源
         */
        @Before("execution(* com.zydd.admin.dao.third..*(..))")
        public void doDW(){
            log.info("选择数据源---" + DataSourceEnums.THIRD.getValue());
            DataSourceContextHolder.setDataSource(DataSourceEnums.THIRD.getValue());
        }
    
        
        /**
         * 通过注解 变更数据源
         * @param joinPoint
         */
        private void changeDataSource(ProceedingJoinPoint joinPoint) {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            MyDataSource myDataSource = null;
            //优先判断方法上的注解
            if (method.isAnnotationPresent(MyDataSource.class)) {
                myDataSource = method.getAnnotation(MyDataSource.class);
                DataSourceContextHolder.setDataSource(myDataSource.value().getValue());
            } else if (method.getDeclaringClass().isAnnotationPresent(MyDataSource.class)) { //其次判断类上的注解
                myDataSource = method.getDeclaringClass().getAnnotation(MyDataSource.class);
                DataSourceContextHolder.setDataSource(myDataSource.value().getValue());
            }
         if (myDataSource != null) {
         log.info("注解方式选择数据源---" + myDataSource.value().getValue());
         }
      } 
    }

     注意:我们不但默认通过 Mapper 的路径来切换数据源,还通过 Service 方法层来切换数据源。

      因为如果 service 有事务的话,进入service方法的时候,DataSourceTransactionManager 就设置好了默认数据源,就算通过Mapper层重新设置数据源,

      DataSourceTransactionManager 的默认数据源还是没有变。

      所以在 事务管理器 设置默认数据源之前,就切换数据源,实现动态事务+动态数据源。

      6.实际使用,只要 MyDataSource 注解就ok了。也可以在 ServiceImpl 类上加注解。

        @Override
        @MyDataSource(DataSourceEnums.THIRD)
        @Transactional
        public void test() {
            DwUserMPEntity test2 = new DwUserMPEntity();
            test2.setUuid("test");
            test2.setNickname("test");
            test2.setPhone("test");
            dwUserDao.insert(test2);
    //        throw new RuntimeException("lala");
        }
    
        @Override
        @MyDataSource(DataSourceEnums.PRIMARY)
        @Transactional
        public void test1() {
            SysRoleMPEntity test = new SysRoleMPEntity();
            test.setRole("test");
            test.setDescription("test");
            sysRoleDao.insert(test);
    //        throw new RuntimeException("lala");
        }

    五.问题总结

    1.配置文件里 mybatis-plus的配置不生效:因为我们在 SqlSessionFactory 里重新写了 MybatisConfiguration 。

    2.启用事务的话,动态数据源不生效:因为 service 有事务的话,在进入service方法时,DataSourceTransactionManager 就设置好了默认数据源。

  • 相关阅读:
    数据库访问优化之四:减少数据库服务器CPU运算
    数据库访问优化之三:减少交互次数
    数据库访问优化之二:返回更少的数据
    数据库访问优化之一:减少数据访问
    数据库性能优化
    磁盘映射
    strcmp
    程序设计基础
    Linux——【rpm、yun、源码包】安装
    Rsa2加密报错java.security.spec.InvalidKeySpecException的解决办法
  • 原文地址:https://www.cnblogs.com/zhaww/p/12706941.html
Copyright © 2020-2023  润新知