• grpc + springboot + mybatis-plus 动态配置数据源


    前言

    这是我在这个网站整理的笔记,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱

    一. 源码解析

    1.1 项目初始化

    项目初始化的时候会调用com.baomidou.dynamic.datasource.DynamicRoutingDataSource对象的addDataSource方法添加数据源,数据源存进dataSourceMap中。

    请添加图片描述

    1.2 接口请求时候

    进行数据操作时,方法会被com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationInterceptor拦截

    请添加图片描述

    intercept 方法中,会解析方法上的 @DS 注解,获取注解中指定的数据源名称。然后,它会调用 DynamicDataSourceContextHolder 类的 setDataSource 方法来切换数据源。

    1692177342027

    DynamicDataSourceContextHolder 是 MyBatis-Plus 提供的一个线程安全的上下文工具类,用于保存当前线程使用的数据源名称。

    进行数据操作时,会调用org.springframework.jdbc.datasource.getConnection()方法;getConnection()方法最终调用了com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource的getConnection()方法

    1692178232591

    上面的determineDataSource是由子类com.baomidou.dynamic.datasource.DynamicRoutingDataSource实现,可以看到DynamicRoutingDataSource从DynamicDataSourceContextHolder获取数据源名称

    1692179899274

    请添加图片描述

    此时的datasource已经切换成了我们需要的数据源

    4、数据操作完成后,方法返回第二步中的拦截器,执行DynamicDataSourceContextHolder.poll();清除掉此次Threadlocal中的数据源,避免影响后续数据操作。

    注意:不可在事务中切换数据库,保证事务需要方法使用同一连接,使用@DS(dataSourceOne)方法调用@DS(dataSourceTwo)无法切换连接,会导致方法报错。

    二. web应用

    参考文档

    1. 引入依赖dynamic-datasource-spring-boot-starter
    <dependency>
      <groupId>com.baomidougroupId>
      <artifactId>dynamic-datasource-spring-boot-starterartifactId>
      <version>${version}version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 配置数据源
    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
        dynamic:
          # 设置默认的数据源或者数据源组,默认值即为 master
          primary: master
          datasource:
            # 主库数据源
            master:
              driverClassName: org.postgresql.Driver
              url: jdbc:postgresql://xx.xx.xx.xx:5432/hwaf?currentSchema=common
              username: root
              password: 123456
            # 主库数据源
            master1:
              driverClassName: org.postgresql.Driver
              url: jdbc:postgresql://xx.xx.xx.xx:5432/hwaf?currentSchema=common
              username: root
              password: 123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 使用 @DS 切换数据源。

    @DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

    注解结果
    没有@DS默认数据源
    @DS(“dsName”)dsName可以为组名也可以为具体某个库的名称
    @Service
    @DS("slave")
    public class UserServiceImpl implements UserService {
    
      @Autowired
      private JdbcTemplate jdbcTemplate;
    
      public List selectAll() {
        return  jdbcTemplate.queryForList("select * from user");
      }
      
      @Override
      @DS("slave_1")
      public List selectByCondition() {
        return  jdbcTemplate.queryForList("select * from user where age >10");
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 根据传递的参数动态切换数据源

    您可以按照以下步骤使用mybatis-plush的dynamic-datasource-spring-boot-starter来根据前端传递的参数动态切换数据源:

    在您的Spring Boot应用程序中添加dynamic-datasource-spring-boot-starter依赖项。

    在应用程序配置文件中定义数据源配置信息,包括主数据源和其他数据源。

    在需要动态切换数据源的方法上使用@DS注释指定数据源,例如:

    @DS("#dataSourceName")
    public List<User> getUserList(String dataSourceName) {
        // 方法实现
    }
    
    • 1
    • 2
    • 3
    • 4

    在这个例子中,#dataSourceName表示从方法参数中获取数据源名称,并将其用作数据源选择策略。

    当调用getUserList方法时,将要使用的数据源名称作为参数传递。例如:

    List<User> userList = userService.getUserList("dataSource2");
    
    • 1

    这将使用名为dataSource2的数据源来执行getUserList方法。

    这个在mybatis-plush官方文档中就有了,接下来说的才是我在开发过程中遇到的问题和解决方案

    三. grpc应用程序

    我想实现的需求是根据传递的参数动态切换数据源,可是我使用的是grpc,没办法像web一样在接口去传递参数

    1692236885912

    为了改动最小,我打算使用拦截器的方式获取传递的值

    @Slf4j
    @RequiredArgsConstructor(onConstructor_ = @Autowired)
    @Component
    public class DataSourceInterceptor implements ServerInterceptor {
    
        private static final Metadata.Key<String> DATA_SOURCE_KEY =
            Metadata.Key.of("data-source", Metadata.ASCII_STRING_MARSHALLER);
    
        @Override
        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
            ServerCall<ReqT, RespT> call,
            Metadata headers,
            ServerCallHandler<ReqT, RespT> next) {
            String dataSource = headers.get(DATA_SOURCE_KEY);
            if (dataSource != null) {
    
               DynamicDataSourceContextHolder.setDataSource(dataSource);
            }
    
            return Contexts.interceptCall(Context.current(), call, headers, next);
    
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    然后起一个上下文线程去存储这个值

    public class DynamicDataSourceContextHolder {
    
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        public static void setDataSource(String dataSource) {
            CONTEXT_HOLDER.set(dataSource);
        }
    
        public static String getDataSource() {
            return CONTEXT_HOLDER.get();
        }
    
        public static void clearDataSource() {
    
            CONTEXT_HOLDER.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    然后重写DsProcessor对象中的doDetermineDatasource,目的是为了能够获取你存储在DynamicDataSourceContextHolder的数据源参数

    请添加图片描述

    @Component
    public class DsMetaProcessor extends DsProcessor {
        private static final String DATE_PREFIX = "#dataSource";
    
        public DsMetaProcessor() {
        }
    
    
        @Override
        public boolean matches(String key) {
            return key.startsWith("#dataSource");
        }
    
        @Override
        public String doDetermineDatasource(MethodInvocation invocation, String key) {
    
            try {
                return DynamicDataSourceContextHolder.getDataSource();
            } finally {
                // 在执行后清理数据源
                DynamicDataSourceContextHolder.clearDataSource();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    最后在方法接口上通过以下方式注入就可以了

    1692237675155

    作者:神的孩子都在歌唱
    本人博客:https://blog.csdn.net/weixin_46654114
    转载说明:务必注明来源,附带本人博客连接。

  • 相关阅读:
    JDBC在IDEA中配置mysql过程及编程详解
    CodeTON Round 3 (Div. 1 + Div. 2, Rated, Prizes!) D. Count GCD
    日常随笔——linux 更换cmake 版本
    Databend JSON 复杂数据类型的设计与使用 | Databend 特性系列
    【Python画图-驯化01】一文叫你搭建python画图最优环境配置
    2022-08-26 Unity视频播放1——视频介绍
    若依如何实现添加水印功能
    SpringBoot的 8 个优点
    【毕业设计】基于单片机的手势识别系统 - 手势识别 单片机 物联网
    MySQL 数据库 查询定义参数【模糊查询】
  • 原文地址:https://blog.csdn.net/weixin_46654114/article/details/132674157