RuoYi-Vue-Plus (多数据源配置)

一、依赖以及配置

1.1依赖

ruoyi-common 下面引入依赖


        <!-- dynamic-datasource 多数据源-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        </dependency>

父pom.xml 下面,控制版本

   <!-- dynamic-datasource 多数据源-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>${dynamic-ds.version}</version>
            </dependency>

 对应版本:

<dynamic-ds.version>3.5.2</dynamic-ds.version>
 1.2  yml中配置
  •  datasource: 配置项下面配置了master、slave2个数据源
  • master、slave 这2个数据源 共用hikari连接池配置
  • 此处演示了oracle 等其他数据源配置
--- # 数据源配置
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
    dynamic:
      # 性能分析插件(有性能损耗 不建议生产环境使用)
      p6spy: true
      # 设置默认的数据源或者数据源组,默认值即为 master
      primary: master
      # 严格模式 匹配不到数据源则报错
      strict: true
      datasource:
        # 主库数据源
        master:
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          # jdbc 所有参数配置参考 https://lionli.blog.youkuaiyun.com/article/details/122018562
          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
          url: jdbc:mysql://localhost:3306/ry-vue-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
        # 从库数据源  lazy: true 用不到不会加载从库
        slave:
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/ry-vue-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
#        oracle:
#          type: ${spring.datasource.type}
#          driverClassName: oracle.jdbc.OracleDriver
#          url: jdbc:oracle:thin:@//localhost:1521/XE
#          username: ROOT
#          password: root
#        postgres:
#          type: ${spring.datasource.type}
#          driverClassName: org.postgresql.Driver
#          url: jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=utf8&useSSL=true&autoReconnect=true&reWriteBatchedInserts=true
#          username: root
#          password: root
#        sqlserver:
#          type: ${spring.datasource.type}
#          driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
#          url: jdbc:sqlserver://localhost:1433;DatabaseName=tempdb;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true
#          username: SA
#          password: root
      hikari:
        # 最大连接池数量
        maxPoolSize: 20
        # 最小空闲线程数量
        minIdle: 10
        # 配置获取连接等待超时的时间
        connectionTimeout: 30000
        # 校验超时时间
        validationTimeout: 5000
        # 空闲连接存活最大时间,默认10分钟
        idleTimeout: 600000
        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
        maxLifetime: 1800000
        # 多久检查一次连接的活性
        keepaliveTime: 30000

解释:

 type: ${spring.datasource.type} ,引用的是配置:
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource

 二、连接测试

2.1未加上多数据源注解

默认连接主库:

@RestController
@Slf4j
@SaIgnore
@RequiredArgsConstructor
public class DynamicController {
    private final ITestDemoService iTestDemoService;

    @GetMapping("testDynamic1")
    public void test() {
        TestDemoVo testDemoVo = iTestDemoService.queryById(2L);
        Console.log("打印数据:{}", testDemoVo);

    }
}

打印结果:
 

打印数据:TestDemoVo(id=2, deptId=102, userId=3, orderNum=2, testKey=主库节点, value=22222, createTime=Tue Jul 23 10:18:35 GMT+08:00 2024, createBy=null, updateTime=null, updateBy=null)
2024-07-23 10:39:55 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic1],耗时:[49]毫秒

2.2 加上多数据源注解 

 @DS("slave")后,打印从库节点数据,如下:

  @GetMapping("testDynamic1")
    @DS("slave")
    public void test() {
        TestDemoVo testDemoVo = iTestDemoService.queryById(2L);
        Console.log("打印数据:{}", testDemoVo);

    }

打印结果:

打印数据:TestDemoVo(id=2, deptId=102, userId=3, orderNum=2, testKey=从库节点, value=22222, createTime=Tue Jul 23 10:18:35 GMT+08:00 2024, createBy=null, updateTime=null, updateBy=null)
2024-07-23 11:14:09 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic1],耗时:[235]毫秒

 注意,失效场景:

  • 持久层使用 @DS注解,只会在自定义的方法上生效
  • 如果标注在 BaseMapper  接口是不会生效,还是查询主库,原因是selectById 是内部调用this.,如下代码。而@DS注解的AOP是对内部调用无法产生作用。所以注解要写在AOP可以作用的地方
  •    /**
         * 根据 ID 查询
         */
        default <C> C selectVoById(Serializable id, Class<C> voClass) {
            T obj = this.selectById(id);
            if (ObjectUtil.isNull(obj)) {
                return null;
            }
            return BeanCopyUtils.copy(obj, voClass);
        }

  • 重点:@DS注解注解,推荐写在实现类的方法上

 错误演示:selectById是MyBatis是自带事项方法,不生效如下;

  /**
     *不会生效:selectById 是 BaseMapper 定义接口
     */
    @Override
    @DS("slave")
    TestDemo selectById(Serializable id);

    /**
     *会生效:selectByIdOfMy 是自定义接口
     */
    @DS("slave")
    TestDemo selectByIdOfMy(Serializable id);

 

三、轮询访问

3.1演示
  • 如图下面配置3个数据源 :slave_1、slave_2、slave_3
  • 如果注解还是这样写:    @DS("slave")  ,则会循环调用3个数据源(默认机制)
   @GetMapping("testDynamic1")
    @DS("slave")
    public void test() {
        TestDemoVo testDemoVo = iTestDemoService.queryById(2L);
        Console.log("打印数据:{}", testDemoVo);

    }

打印结果:

打印数据:TestDemoVo(id=2, deptId=102, userId=3, orderNum=2, testKey=从库节点1, value=111, createTime=Tue Jul 23 10:18:35 GMT+08:00 2024, createBy=null, updateTime=null, updateBy=null)
2024-07-23 11:20:09 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic1],耗时:[235]毫秒


打印数据:TestDemoVo(id=2, deptId=102, userId=3, orderNum=2, testKey=从库节点2, value=222, createTime=Tue Jul 23 10:18:35 GMT+08:00 2024, createBy=null, updateTime=null, updateBy=null)
2024-07-23 11:21:11 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic1],耗时:[212]毫秒



打印数据:TestDemoVo(id=2, deptId=102, userId=3, orderNum=2, testKey=从库节点3, value=333, createTime=Tue Jul 23 10:18:35 GMT+08:00 2024, createBy=null, updateTime=null, updateBy=null)
2024-07-23 11:22:13 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic1],耗时:[187]毫秒

 slave_1、slave_2、slave_3 数据源配置:

--- # 多数据源配置 HikariDataSource
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    # 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
    dynamic:
      # 性能分析插件(有性能损耗 不建议生产环境使用)
      p6spy: true
      # 设置默认的数据源或者数据源组,默认值即为 master
      primary: master
      # 严格模式 匹配不到数据源则报错
      strict: true
      datasource:
        # 主库数据源1
       slave_1:
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          # jdbc 所有参数配置参考 https://lionli.blog.youkuaiyun.com/article/details/122018562
          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
          url: jdbc:mysql://localshost:3306/ry-vue-plus?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
        # 从库数据源2
        slave_2:
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localshost:3306/ry-vue-plus2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
         # 从库数据源3
        slave_3:
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localshost:3306/ry-vue-plus2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
3.2数据源策略
  1. DynamicDataSourceProperties.class 定义了每一个数据源,以及对应动态数据源策略
  2. LoadBalanceDynamicDataSourceStrategy就是默认负载均衡策略
    /**
     * 每一个数据源
     */
    private Map<String, DataSourceProperty> datasource = new LinkedHashMap<>();
    /**
     * 多数据源选择算法clazz,默认负载均衡算法
     */
    private Class<? extends DynamicDataSourceStrategy> strategy = LoadBalanceDynamicDataSourceStrategy.class;

 查看器策略接口:com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy  的实现类有2个:

  • RandomDynamicDataSourceStrategy.class  随机策略
  • LoadBalanceDynamicDataSourceStrategy 负载均衡策略(默认

3.3 配置策略

指定 strategy 属性就行如下,指定RandomDynamicDataSourceStrategy随机策略:

。。。。。。。省里配置     
 hikari:
        # 最大连接池数量
        maxPoolSize: 20
        # 最小空闲线程数量
        minIdle: 10
        # 配置获取连接等待超时的时间
        connectionTimeout: 30000
        # 校验超时时间
        validationTimeout: 5000
        # 空闲连接存活最大时间,默认10分钟
        idleTimeout: 600000
        # 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
        maxLifetime: 1800000
        # 多久检查一次连接的活性
        keepaliveTime: 30000
  strategy: com.baomidou.dynamic.datasource.strategy.RandomDynamicDataSourceStrategy
### 数据源动态加载方法 在 RuoYi-Vue-Plus 中,为了支持从数据库中动态加载数据源的功能,通常采用 Spring Boot 的 `AbstractRoutingDataSource` 或者自定义的数据源切换逻辑来实现。以下是具体的实现方式: #### 1. **配置文件中的初始数据源** 在项目的 `application.yml` 文件中,可以预先设置默认的核心数据源(Core DataSource)。该核心数据源用于连接到存储其他动态数据源信息的表。 ```yaml spring: datasource: core: jdbc-url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 12345678 ``` 此部分配置为核心数据源的信息[^1]。 --- #### 2. **创建动态数据源管理类** 通过继承 `AbstractRoutingDataSource` 类或者手动维护一个线程安全的 Map 来保存动态数据源实例。 ##### 动态数据源工具类代码示例: ```java import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); } } ``` 在此处实现了基于当前线程上下文决定使用哪个数据源的关键逻辑[^2]。 --- #### 3. **初始化动态数据源** 当应用启动时,可以通过读取数据库中的配置表(例如 `sys_datasource` 表),将其中记录的所有数据源信息注册到动态数据源容器中。 ##### 初始化逻辑伪代码: ```java @Service public class DataSourceInitService { @Autowired private DruidDynamicDataSource dynamicDataSource; @PostConstruct public void initDatasourcesFromDB() { List<SysDataSourceEntity> sysDataSources = dataSourceRepository.findAll(); // 假设有一个 Repository 查询所有数据源 sysDataSources.forEach(sysDataSource -> { HikariDataSource ds = createDataSource(sysDataSource.getUrl(), sysDataSource.getUsername(), sysDataSource.getPassword()); dynamicDataSource.addDataSource(sysDataSource.getName(), ds); // 将新数据源加入动态池 }); } private HikariDataSource createDataSource(String url, String username, String password) { HikariConfig config = new HikariConfig(); config.setJdbcUrl(url); config.setUsername(username); config.setPassword(password); return new HikariDataSource(config); } } ``` 这段代码展示了如何从数据库中获取多个数据源并将其注入到动态数据源管理器中[^3]。 --- #### 4. **切换单个请求使用的数据源** 在实际业务场景下,可能需要根据某些条件(如租户 ID、模块名等)动态切换数据源。这一步骤通常是通过拦截器或 AOP 实现的。 ##### 切换数据源的拦截器示例: ```java @Component public class DataSourceInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String tenantId = request.getHeader("X-Tenant-ID"); // 获取租户ID或其他标识符 if (StringUtils.hasText(tenantId)) { DynamicDataSourceContextHolder.setDataSourceKey(tenantId); // 设置对应的数据源键值 } return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { DynamicDataSourceContextHolder.clearDataSourceKey(); // 清理线程变量 } } ``` 以上代码片段说明了如何利用 HTTP 请求头传递参数完成数据源的选择操作[^4]。 --- #### 总结 综上所述,在 RuoYi-Vue-Plus 项目中实现从数据库加载数据源的主要思路包括以下几个方面: 1. 使用静态配置作为核心数据源; 2. 创建动态数据源管理组件,并扩展其行为以适应多数据源需求; 3. 在程序运行期间实时更新可用数据源列表; 4. 结合具体业务规则灵活调整每次查询所依赖的具体数据源。 这种设计模式不仅增强了系统的灵活性,还能够很好地满足分布式架构下的复杂应用场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

syfjava

请博主喝杯蜜雪冰城

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值