柏竹 柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档

柏竹

奋斗柏竹
首页
后端
前端
  • 应用推荐
关于
友链
  • 分类
  • 标签
  • 归档
  • Java基础

  • JavaWeb

  • 拓展技术

  • 框架技术

  • 数据库

  • 数据结构

  • Spring

  • SpringMVC

  • SpringBoot

  • SpringClound

  • Ruoyi-Vue-Plus

    • ruoyi-vue-plus-基础功能
    • ruoyi-vue-plus-权限控制
    • ruoyi-vue-plus-表格操作
    • ruoyi-vue-plus-缓存功能
    • ruoyi-vue-plus-日志功能
    • ruoyi-vue-plus-线程相关
    • ruoyi-vue-plus-OSS功能
    • ruoyi-vue-plus-代码生成功能
    • ruoyi-vue-plus-多数据源
      • 快速应用
        • 依赖
        • 多数据源基础配置
        • 切换数据源
      • 数据源配置
      • 动态切换数据源
      • 动态解析数据源
      • 负载均衡配置
      • 动态管理数据源
      • 事务管理
      • 拦截器动态切换数据源
      • 数据源工具
    • ruoyi-vue-plus-任务调度
    • ruoyi-vue-plus-监控功能
    • ruoyi-vue-plus-国际化
    • ruoyi-vue-plus-XSS功能
    • ruoyi-vue-plus-防重幂&限流 功能
    • ruoyi-vue-plus-推送功能
    • ruoyi-vue-plus-序列化功能
    • ruoyi-vue-plus-数据加密
    • ruoyi-vue-plus-单元测试
    • ruoyi-vue-plus-前端插件
    • ruoyi-vue-plus-前端工具篇
    • ruoyi-vue-plus-部署篇
    • ruoyi-vue-plus-前端篇
    • ruoyi-vue-plus-后端工具篇
    • ruoyi-vue-plus-框架篇
    • ruoyi-vue-plus-问题解决
  • 后端
  • Ruoyi-Vue-Plus
柏竹
2024-01-01
目录

ruoyi-vue-plus-多数据源

集成SpringBoot多数据源应用 , 基于AOP切面实现多数据源切换应用

参考文档

  • 官方文档 : https://www.kancloud.cn (opens new window)
  • ruoyi-vue-plus应用文档 : https://plus-doc.dromara.org (opens new window)

# 快速应用

大致步骤

  1. 引入依赖
  2. 配置数据源基础信息
  3. 切换数据源
  4. 应用...

# 依赖

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

# 多数据源基础配置

基础应用示例

点击展开
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.csdn.net/article/details/122018562
          # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
          url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username: root
          password: root
        # 从库数据源 (开启懒加载)
        slave:
          lazy: true
          type: ${spring.datasource.type}
          driverClassName: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
          username:
          password:
# 其他数据源配置 (替换掉从数据库即可)
#        oracle:
#          type: ${spring.datasource.type}
#          driverClassName: oracle.jdbc.OracleDriver
#          url: jdbc:oracle:thin:@//localhost:1521/XE
#          username: ROOT
#          password: root
#          hikari:
#            connectionTestQuery: SELECT 1 FROM DUAL
#        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

:::

注意

  • 不支持 Service接口中的方法使用 @DS注解
  • 不支持 Mapper层中的默认方法不能使用 @DS注解 , 但支持自定义方法使用 @DS注解

# 切换数据源

在 类/方法 中配置 @DS注解 , 其注解的值是对应上面从数据源的名称

@GetMapping("/test1")
@DS("slave")
public R<String> test() {
    log.info("多数据源应用测试");
    TestDemoVo vo = testDemoService.queryById(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}
// 测试结果观察不同库中的字段即可

# 数据源配置

配置数据源一般会在 datasource节点 下配置多个不同dsName数据源

配置形式

# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:

# 动态切换数据源

切换数据源有两种方式

  • 声明式 , 采用 @DS注解 实现 (示例跳转)
  • 编程式 , 采用 DynamicDataSourceContextHolder数据源切换工具类

DynamicDataSourceContextHolder类 , 有如下功能 :

返回 方法 说明
String peek() 获得 当前线程数据源名
String push(String ds) 设置 当前线程数据源 (切换数据源)
void poll() 清空 当前线程数据源
void clear() 清空 本地线程 (强制)

编程式示例

@GetMapping("/test2")
public R<String> test2() {
    TestDemoVo vo = testDemoService.queryById(3L);
    String peek = DynamicDataSourceContextHolder.peek();
    log.info("peek1 => {}\n vo1 => {}", peek, vo);
    log.info("============切换至 slave_1");
    // 切换数据源
    DynamicDataSourceContextHolder.push("slave_1");
    TestDemoVo vo2 = testDemoService.queryById(3L);
    String peek2 = DynamicDataSourceContextHolder.peek();
    DynamicDataSourceContextHolder.poll();
    log.info("peek2 => {}\n vo2 => {}", peek2, vo2);
    log.info("============清空当前数据源 , 回到主数据源");
    // 使用主数据源
    TestDemoVo vo3 = testDemoService.queryById(3L);
    String peek3 = DynamicDataSourceContextHolder.peek();
    log.info("peek3 => {}\n vo3 => {}", peek3, vo3);
    DynamicDataSourceContextHolder.clear();
    return R.ok("多数据源应用测试");
}
// 测试结果观察不同库中的字段即可

提示

编程式可以在代理模式实现 . 拦截器路由控制数据源切换 点击跳转

注意

异步线程需要重新切换数据源

# 动态解析数据源

通过 @DS注解 实现对dsName数据源解析访问

支持方式应用 :

  • 指定dsName数据源名称
  • 请求头参数 (header)
  • 会话参数 (session)
  • 方法参数

指定dsName数据源名称

匹配配置类中的 datasource节点 下的不同dsName数据源

点击展开

 







@GetMapping("/test1")
@DS("slave")
public R<String> test() {
    log.info("多数据源应用测试");
    TestDemoVo vo = testDemoService.queryById(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}

请求头参数

通过 #header 访问请求头对象

点击展开
 







@DS("#header.datasource")
@GetMapping("/test3")
public R<String> test3() {
    TestDemoVo vo = testDemoService.queryById(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}

方法参数

点击展开
// 单参数
@DS("#datasource")
@GetMapping("/test4/{datasource}")
public R<String> test4(@PathVariable String datasource) {
    TestDemoVo vo = testDemoService.queryById2(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}
// 对象参数 
// 通过 testKey指定 dsName数据源名
@DS("#bo.testKey")
@GetMapping("/test5")
public R<String> temp5(TestDemoBo bo) {
    log.info("bo => {} ", bo);
    TestDemoVo vo = testDemoService.queryById2(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}

提示

@DS注解 切换数据源也支持在类中应用 , 一般情况不建议

如果类和方法同时使用 @DS注解 , 那么优先应用方法中的注解 (就近原则)

# 负载均衡配置

本框架默认采用轮询策略实现负载均衡 . 将多个从数据源编排为组 , 以组的形式进行轮询 , 前缀作为组名 , 中间以 _ 分割 , 后缀作为序号

语法 : <dsName组名>_<序号>

策略类型

  • 轮询策略 (默认)
  • 随机策略

示例

# 多主多从
spring:
  datasource:
    dynamic:
      # strategy: com.baomidou.dynamic.datasource.strategy.LoadBalanceDynamicDataSourceStrategy # 轮询策略配置(默认)
	  strategy: com.baomidou.dynamic.datasource.strategy.RandomDynamicDataSourceStrategy # 随机策略配置
      datasource:
        master:
        slave_1:
        slave_2:
        slave_3:

实现策略

根据 dsName组名 匹配后缀的序号 (slave_1、slave_2、slave_3)

@GetMapping("/test1")
@DS("slave")
public R<String> test() {
    log.info("多数据源应用测试");
    TestDemoVo vo = testDemoService.queryById(3L);
    log.info("vo => {}", vo);
    return R.ok("多数据源应用测试");
}

# 动态管理数据源

在运行期间动态的管理数据源正删改查 , 通过 DynamicRoutingDataSource类 核心组件实现管理

DynamicRoutingDataSource类 是管理数据源集合的对象 , 当中有多种方法进行对数据源操作管理

DynamicRoutingDataSource类 常用方法

返回 方法 说明
Map<String, DataSource> getDataSources() 获取 所有数据源
Map<String, GroupDataSource> getGroupDataSources() 获取 所有数据源组
DataSource getDataSource(String ds) 获取 指定dsName数据源
void addDataSource(String ds, DataSource dataSource) 添加 数据源
void addGroupDataSource(String ds, DataSource dataSource) 添加 数据源组
void removeDataSource(String ds) 删除 指定dsName数据源

代码示例

/**
* 获取所有数据源
*/
// Bean自动注入应用即可 (以下注入采用构造方法隐式注入)
private final DynamicRoutingDataSource dynamicRoutingDataSource;
@GetMapping("/test10")
public R<List<String>> test10() {
    // 查询所有数据源
    Map<String, DataSource> dataSources = dynamicRoutingDataSource.getDataSources();
    dataSources.forEach((k, v) -> log.info("k => {}\n- v => {}\n===============", k, v));
}

/**
 * 添加数据源
 */
private final DefaultDataSourceCreator dataSourceCreator;
@GetMapping("/test11")
public R<String> test11() {
    // 从数据源配置
    DataSourceProperty property = new DataSourceProperty();
    property.setDriverClassName("com.mysql.cj.jdbc.Driver");
    property.setType(HikariDataSource.class);
    property.setUrl("jdbc:mysql://localhost:3306/****?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true");
    property.setUsername("root");
    property.setPassword("root");
    property.setPoolName("bzPool");

    // 创建数据源
    DataSource creatorDataSource = dataSourceCreator.createDataSource(property);
    dynamicRoutingDataSource.addDataSource(property.getPoolName(), creatorDataSource);

    return R.ok("添加成功");
}

/**
 * 移除数据源
 */
@GetMapping("/test12")
public R<String> test12() {
    // 从数据源查询
    dynamicRoutingDataSource.removeDataSource("bzPool");
    return R.ok("移除成功");
}

更多应用等功能自行测试

# 事务管理

在数据源通信中一般是一个连接一个事务管理 , 一旦涉及多数据源那么常规的事务管理将会失去效果

该框架有解决方案 , 在最外层的方法添加 @DSTransactional注解 , 实现多数据源事务管理 (基于AOP实现)

示例

点击展开

Controller层

@GetMapping("/test7")
@DSTransactional
public R<String> test7() {
    boolean b1 = testDemoService.delById1(2L);
    log.info("b1 => {}", b1);
    boolean b2 = testDemoService.delById2(3L);
    log.info("b2 => {}", b2);
    return R.ok("多数据源应用测试");
}

Service层

@DS("slave_1")
@Override
public boolean delById1(Long id) {
    String peek = DynamicDataSourceContextHolder.peek();
    System.out.println("peek1 = " + peek);
    return baseMapper.deleteById(id) > 0;
}

@DS("slave_2")
@Override
public boolean delById2(Long id) {
    // 异常回滚测试
    //if (true) throw new ServiceException("异常");
    String peek = DynamicDataSourceContextHolder.peek();
    System.out.println("peek2 = " + peek);
    return baseMapper.deleteById(id) > 0;
}

# 拦截器动态切换数据源

拦截器切换可以根据请求信息任意控制数据源的切换 (SpringBoot拦截器如何配置? (opens new window))

示例

/**
 * 拦截所有路由即可 (特殊业务自行增加拦截器配置等信息)
 */
public class DynamicInterceptor implements HandlerInterceptor {
    /**
     * 请求前 切换数据源
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 拦截携带有 slave1 , slave1 的url控制切换
        String rui = request.getRequestURI();
        // 主数据源
        String ds = "master";

        if (rui.contains("slave1")) {
            ds = "slave1";
        }
        if (rui.contains("slave2")) {
            ds = "slave2";
        }
        // DIY 路由控制切换数据源
        DynamicDataSourceContextHolder.push(ds);

        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    /**
     * 请求结束后清空当前线程的数据源
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        DynamicDataSourceContextHolder.clear();
    }
}

# 数据源工具

DataBaseHelper工具类

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class DataBaseHelper {

    private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);

    /**
     * 获取当前数据库类型
     */
    public static DataBaseType getDataBaseType() {
        DataSource dataSource = DS.determineDataSource();
        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData metaData = conn.getMetaData();
            String databaseProductName = metaData.getDatabaseProductName();
            return DataBaseType.find(databaseProductName);
        } catch (SQLException e) {
            throw new ServiceException(e.getMessage());
        }
    }

    public static boolean isMySql() {
        return DataBaseType.MY_SQL == getDataBaseType();
    }

    public static boolean isOracle() {
        return DataBaseType.ORACLE == getDataBaseType();
    }

    public static boolean isPostgerSql() {
        return DataBaseType.POSTGRE_SQL == getDataBaseType();
    }

    public static boolean isSqlServer() {
        return DataBaseType.SQL_SERVER == getDataBaseType();
    }

    // 通用兼容数据源拼接模板
    public static String findInSet(Object var1, String var2) {
        DataBaseType dataBasyType = getDataBaseType();
        String var = Convert.toStr(var1);
        if (dataBasyType == DataBaseType.SQL_SERVER) {
            // charindex(',100,' , ',0,100,101,') <> 0
            return "charindex('," + var + ",' , ','+" + var2 + "+',') <> 0";
        } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
            // (select position(',100,' in ',0,100,101,')) <> 0
            return "(select position('," + var + ",' in ','||" + var2 + "||',')) <> 0";
        } else if (dataBasyType == DataBaseType.ORACLE) {
            // instr(',0,100,101,' , ',100,') <> 0
            return "instr(','||" + var2 + "||',' , '," + var + ",') <> 0";
        }
        // find_in_set('100' , '0,100,101')
        return "find_in_set('" + var + "' , " + var2 + ") <> 0";
    }
}
#ruoyi-vue-plus

← ruoyi-vue-plus-代码生成功能 ruoyi-vue-plus-任务调度→

最近更新
01
HTTPS自动续签
10-21
02
博客搭建-简化版(脚本)
10-20
03
ruoyi-vue-plus-部署篇
07-13
更多文章>
Theme by Vdoing | Copyright © 2019-2024 | 桂ICP备2022009417号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式