RuoYi-Vue-Plus (多数据源注解使用、【手动、拦截器】切换数据源)

接上文多数据源配置:

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

一、功能演示

代码生成菜单页面, 展示数据源切换

查询主库

 查询从库

二、前端传参切换数据源

页面路径: src/views/tool/gen/index.vue

搜索框如下:下面4发送请求时候,在header带上 要切换数据库

headers: { 'datasource': localStorage.getItem("dataName") },

 前端输入框,到发送请求代码如下

1--页面输入框  
<el-form-item label="数据源" prop="dataName"><el-inputv-model="queryParams.dataName"placeholder="请输入数据源名称"clearable@keyup.enter.native="handleQuery"/></el-form-item>2--搜索操作/** 搜索按钮操作 */handleQuery() {localStorage.setItem("dataName", this.queryParams.dataName);this.queryParams.pageNum = 1;this.getList();},3-- 查询表集合 getList() {this.loading = true;listTable(this.addDateRange(this.queryParams, this.dateRange)).then(response => {this.tableList = response.rows;this.total = response.total;this.loading = false;});},4-查询生成表数据
export function listTable(query) {return request({headers: { 'datasource': localStorage.getItem("dataName") },url: '/tool/gen/list',method: 'get',params: query})
}

 后台实现类标记 @DS("#header.datasource")注解,就完成通过head切换

@DS("#header.datasource")
@Slf4j
@RequiredArgsConstructor
@Service
public class GenTableServiceImpl implements IGenTableService {
。。。。。省略代码

三、字符串、实体类接受参数切换数据源

3.1 字符串
  1. 请求时候带上name入参:GET http://localhost:8080/testDynamic2?name=slave
  2. @DS("#name") 实现类标注

//controller@GetMapping("testDynamic2")public void testDynamic2(String name) {TestDemoVo testDemoVo = iTestDemoService.queryById(name,2L);Console.log("打印数据:{}", testDemoVo);} //实现类
@Override@DS("#name")public TestDemoVo queryById(String name, Long id) {return baseMapper.selectVoById(id);}

 结果:访问从库成功

打印数据: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-24 17:15:12 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic2],耗时:[33]毫秒

3.2 实体类接受
  1. controller 设置对象的testkey属性值
  2. @DS("#testDemo.testKey")获取数据源
   @GetMapping("testDynamic4")public void testDynamic4() {TestDemo testDemo = new TestDemo();testDemo.setTestKey("slave");TestDemoVo testDemoVo = iTestDemoService.queryById(testDemo,2L);Console.log("打印数据:{}", testDemoVo);}//实现类@Override@DS("#testDemo.testKey")public TestDemoVo queryById(TestDemo testDemo, Long id) {return baseMapper.selectVoById(id);}

  结果:访问从库成功

打印数据: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-24 17:20:12 [XNIO-1 task-1] INFO  c.r.f.i.PlusWebInvokeTimeInterceptor
 - [PLUS]结束请求 => URL[GET /testDynamic2],耗时:[31]毫秒

四、手动切换数据源

4.1 DynamicDataSourceContextHolder 工具类
  1. DynamicDataSourceContextHolder :核心基于ThreadLocal的切换数据源工具
  2.      DynamicDataSourceContextHolder 使用new ArrayDeque<>链表存储(准确的是栈)原因:
  •       为了支持嵌套切换,如ABC三个service都是不同的数据源
  •      其中A的某个业务要调B的方法,B的方法需要调用C的方法。一级一级调用切换,形成了链。
  •      传统的只设置当前线程的方式不能满足此业务需求,必须使用栈,后进先出

   链表申明代码如下;

    private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {@Overrideprotected Deque<String> initialValue() {return new ArrayDeque<>();}};

 该工具类提供了 CRUD,如下:

private DynamicDataSourceContextHolder() {}/*** 获得当前线程数据源** @return 数据源名称*/public static String peek() {return LOOKUP_KEY_HOLDER.get().peek();}/*** 设置当前线程数据源* <p>* 如非必要不要手动调用,调用后确保最终清除* </p>** @param ds 数据源名称*/public static String push(String ds) {String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;LOOKUP_KEY_HOLDER.get().push(dataSourceStr);return dataSourceStr;}/*** 清空当前线程数据源* <p>* 如果当前线程是连续切换数据源 只会移除掉当前线程的数据源名称* </p>*/public static void poll() {Deque<String> deque = LOOKUP_KEY_HOLDER.get();deque.poll();if (deque.isEmpty()) {LOOKUP_KEY_HOLDER.remove();}}/*** 强制清空本地线程* <p>* 防止内存泄漏,如手动调用了push可调用此方法确保清除* </p>*/public static void clear() {LOOKUP_KEY_HOLDER.remove();}
4.2测试手动切换数据源

演示一:手动切换到 slave 从库,并打印结果

@GetMapping("testDynamic5")public void testDynamic5() {TestDemoVo testDemoVo = iTestDemoService.queryById(2L);Console.log("打印数据:{}", testDemoVo);// 打印当前数据源String peek = DynamicDataSourceContextHolder.peek();Console.log("未设置数据源,打印当前数据源:{}", peek);// 切换数据源 slaveConsole.log("切换数据源:slave----------------------");String slave = DynamicDataSourceContextHolder.push("slave");Console.log("已经设置数据源,打印当前数据源:{}", slave);//调用完成:清空当前线程数据源DynamicDataSourceContextHolder.poll();//最后:强制清空本地线程DynamicDataSourceContextHolder.clear();}

运行结果:

打印数据: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)
未设置数据源,打印当前数据源:null
切换数据源:slave----------------------
已经设置数据源,打印当前数据源:slave

 演示二:切换从库后,再次访问主库

代码:

@GetMapping("testDynamic5")public void testDynamic5() {TestDemoVo testDemoVo = iTestDemoService.queryById(2L);Console.log("打印数据:{}", testDemoVo);// 打印当前数据源String peek = DynamicDataSourceContextHolder.peek();Console.log("未设置数据源,打印当前数据源:{}", peek);// 切换数据源 slaveConsole.log("切换数据源:slave----------------------");String slave = DynamicDataSourceContextHolder.push("slave");Console.log("已经设置数据源,打印当前数据源:{}", slave);//调用完成:清空当前线程数据源DynamicDataSourceContextHolder.poll();// 切换数据源 masterConsole.log("切换数据源:master----------------------");String master = DynamicDataSourceContextHolder.push("master");Console.log("已经设置数据源,打印当前数据源:{}", master);//调用完成:清空当前线程数据源DynamicDataSourceContextHolder.poll();//最后:强制清空本地线程DynamicDataSourceContextHolder.clear();}

运行结果:访问主库

打印数据: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)
未设置数据源,打印当前数据源:null
切换数据源:slave----------------------
已经设置数据源,打印当前数据源:slave
切换数据源:master----------------------
已经设置数据源,打印当前数据源:master 

演示三: 切换线程时候访问数据源

private final ThreadPoolTaskExecutor threadPoolTaskExecutor;/*** <简述>new 线程切换数据源* <详细描述>* @author syf* @date 2024/7/24 17:19*/@GetMapping("testDynamic6")public void testDynamic6() {TestDemoVo testDemoVo = iTestDemoService.queryById(2L);Console.log("打印数据:{}", testDemoVo);threadPoolTaskExecutor.submit(() -> {Console.log("切换数据源:slave----------------------");String slave = DynamicDataSourceContextHolder.push("slave");Console.log("已经设置数据源,打印当前数据源:{}", slave);TestDemoVo testDemoVo2 = iTestDemoService.queryById(2L);Console.log("新线程打印数据:{}", testDemoVo2);//调用完成:清空当前线程数据源DynamicDataSourceContextHolder.poll();//最后:强制清空本地线程DynamicDataSourceContextHolder.clear();});}

 

五、多数据源事务处理

5.1 数据源失效场景

   场景:

       实现类一个调用主库,另外一个  @DS("slave")标注调用从库。结果却是更新主库数据,如下截图:

  •      @Transactional 原生注解标注,会保证整个线程拿到的都是同一个连接,所以上面都更下主库
  •    我们刚进入线程时候用的是主数据源,又因为有@Transactional 所以切换数据源也不生效
  @GetMapping("testDynamic7")@Transactionalpublic void testDynamic7() {iTestDemoService.deleteIdMaster(2L);iTestDemoService.deleteIdSlave(2L);}//实现类的调用@Overridepublic void deleteIdMaster(Long id) {baseMapper.deleteById(id);}@Override@DS("slave")public void deleteIdSlave(Long id) {baseMapper.deleteById(id);}

 执行结果: 标注更新从库,但是删除的是主库

 

 5.2 解决办法

基于上面:

         @Transactional 是基于数据库现的事务

解决:

        @DSTransactional基于AOP实现的事务

  @GetMapping("testDynamic7")@DSTransactionalpublic void testDynamic7() {iTestDemoService.deleteIdMaster(2L);iTestDemoService.deleteIdSlave(2L);}

结果:删除从库

总结:

在需要切换数据源时候使用 @DSTransactional

不需要时候还是使用原生注解:@Transactional

六、拦截器切换数据源

拦截器切换数据源demo演示:

配置类:

@Configuration
public class DynamicDSConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new DynamicInterceptor()).addPathPatterns("/**");}
}

实现类,展示DEMO根据以下三种切换:

  1. 根据request请求判断
  2. 获取请求头参数切换
  3. 根据登录用户切换
@Slf4j
public class DynamicInterceptor implements HandlerInterceptor {//请求处理之前调用@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1-根据请求判断String requestURI = request.getRequestURI();log.info("requestURI:{}", requestURI);String ds = "";if (requestURI.contains("/testDynamic7")){ds = "slave";}//2-根据请求头动态切换String datasource = request.getHeader("datasource");if (StringUtils.isNotBlank(datasource)){ds = datasource;}//3- 更具登录用户动态切换LoginUser loginUser = null;try {loginUser = LoginHelper.getLoginUser();log.info("loginUser:{}", loginUser);if("admin".equals(loginUser.getUsername())){ds = "master";}}catch (Exception e){}DynamicDataSourceContextHolder.push(ds);return true;}//请求处理但是页面未渲染调用@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {}//请求处理完毕调用@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/388728.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

技术分享| 前端性能优化——虚拟滚动(Virtual Scroll)

前端遇到大量数据&#xff08;尤其是大数据表&#xff09;的DOM 渲染时&#xff0c;通常会卡顿&#xff0c;需要考虑优化性能问题&#xff0c;这里针对DOM 渲染引出“虚拟滚动”方案&#xff0c; 详细请在以下各文章中详细了解&#xff1a; vue插件 vue-virtual-scroll-list解决…

干货满满,从零到一:编程小白如何在大学成为编程大神?

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

ClickHouse 24.6 版本发布说明

本文字数&#xff1a;14127&#xff1b;估计阅读时间&#xff1a;36 分钟 作者&#xff1a;ClickHouse team 本文在公众号【ClickHouseInc】首发 又到了发布新版本的时间&#xff01; 发布概要 本次ClickHouse 24.6 版本包含了23个新功能&#x1f381;、24项性能优化&#x1f6…

嵌入式人工智能(39-基于树莓派4B的震动传感器和霍尔传感器)

这两个传感器实验比较简单&#xff0c;也都属于力传感器&#xff0c;就放一起做了。 1、震动传感器 震动传感器是一种用于检测和测量物体震动、振动和冲击的设备。它通常由一个敏感元件和一个信号处理单元组成。敏感元件可以是压电材料、光电材料、加速度传感器等。当物体发生…

【Git】git 从入门到实战系列(一)—— Git 的诞生,Linus 如何在 14 天内编写出 Git?

<> 博客简介&#xff1a;Linux、rtos系统&#xff0c;arm、stm32等芯片&#xff0c;嵌入式高级工程师、面试官、架构师&#xff0c;日常技术干货、个人总结、职场经验分享   <> 公众号&#xff1a;嵌入式技术部落   <> 系列专栏&#xff1a;C/C、Linux、rt…

golang JSON序列化

JSON JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 json历史 [外链图片转存失败,源站可能有防盗链机…

看不见的硝烟:中国网络安全三十年沉浮史

2022 年 5 月 16 日&#xff0c;俄罗斯黑客组织 KillNet 向包括美国、英国、德国在内 10 个国家的政府正式 “宣战”。 2022 年 4 月 28 日&#xff0c;一则消息刷屏&#xff0c;北京健康宝在使用高峰期间&#xff0c;遭受到境外网络攻击。北京健康宝保障团队进行了及时有效应…

4G/5G无线视频采集设备如何通过国标28181接入到视频监控接入平台(视频统一接入平台)

目录 一、国标GB/T 28181介绍 1、国标GB/T28181 2、内容和特点 二、4G/5G无线视频采集设备 1、定义 2、主要功能&#xff1a; 3、技术特点 4、应用场景 二、接入准备工作 1、确定网络环境 &#xff08;1&#xff09;公网接入 &#xff08;2&#xff09;专网传输 2、…

一款功能强大的免费开源卸载工具

BCUninstaller&#xff0c;也称为Bulk Crap Uninstaller&#xff08;简称BCU&#xff09;&#xff0c;是一款免费且开源的Windows平台专用程序卸载工具。它的主要功能是帮助用户高效地批量卸载不需要的应用程序和组件&#xff0c;从而优化系统性能。 BCUninstaller功能特点 批…

这本vue3编译原理开源电子书,初中级前端竟然都能看懂

前言 众所周知vue提供了很多黑魔法&#xff0c;比如单文件组件(SFC)、指令、宏函数、css scoped等。这些都是vue提供的开箱即用的功能&#xff0c;大家平时用这些黑魔法的时候有没有疑惑过一些疑问呢。 我们每天写的vue代码一般都是写在*.vue文件中&#xff0c;但是浏览器却只…

【大厂笔试】翻转、平衡、对称二叉树,最大深度、判断两棵树是否相等、另一棵树的子树

检查两棵树是否相同 100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 思路解透 两个根节点一个为空一个不为空的话&#xff0c;这两棵树就一定不一样了若两个跟节点都为空&#xff0c;则这两棵树一样当两个节点都不为空时&#xff1a; 若两个根节点的值不相同&#xff…

【数据结构】了解哈希表,解决哈希冲突,用Java模拟实现哈希桶

哈希表的概念 哈希表&#xff08;Hash Table&#xff09;是一种高效的数据结构&#xff0c;用于实现快速的数据存储和检索。它通过将数据映射到一个数组的索引位置&#xff0c;从而能够在平均情况下实现O(1)的时间复杂度进行查找、插入和删除操作。 哈希表的基本概念包括以下…

java面向对象重点总结

文章目录 java面向对象重点总结类与实例构造方法方法重载属性与修饰符封装继承多态重构抽象类接口抽象类和接口的区别&#xff1a;集合泛型 java面向对象重点总结 对象是一个自包含的实体&#xff0c;用一组可识别的特性和行为来标识。 面向对象编程&#xff0c;英文叫Object…

flink 1.17 测试

1、配置 2、测试&#xff1a; ./bin/flink run-application -t yarn-application -Dyarn.application.namewordcount -c org.apache.flink.streaming.examples.wordcount.WordCount ./examples/streaming/WordCount.jar --input hdfs://jy/tmp/input --output hdfs://jy/tmp/o…

C++学习:C++是如何运行的

C 是一种强类型的编程语言&#xff0c;支持面向对象、泛型和低级内存操作。它的工作机制包括从编写源代码到生成可执行文件的一系列步骤。C与文件无关&#xff0c;文件只是容纳运行内容的载体&#xff0c;需要对文件以目标系统的规则编译后&#xff0c;才能在目标系统中运行。 …

java算法递归算法练习-数组之和

简单找个题目练习一下递归算法&#xff0c;输入一组数组&#xff0c;使用递归的方法计算数组之和。其实这个题目&#xff0c;用循环的方式也很简单就能解决&#xff0c;直接循环遍历一下相加就行了&#xff0c;但是我们用来练习一下递归。 先来找基线条件和递归条件 基线条件…

springboot+webSocket对接chatgpt

webSocket对接参考 话不多说直接上代码 WebSocket package com.student.config;import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springfram…

matplotLib在图中标出最后一个点的值

import matplotlib.pyplot as plt import numpy as np# 生成100个随机数据 data np.random.rand(100)# 绘制数据 plt.plot(data, labelData Points)# 获取最后一个数据点的位置和值 last_x len(data) - 1 last_y data[-1]# 用红圈标出最后一个点 plt.plot(last_x, last_y, r…

STM32-寄存器DMA配置指南

配置步骤 在STM32F0xx中文参考手册中的DMA部分在开头给出了配置步骤 每个通道都可以在外设寄存器固定地址和存储器地址之间执行 DMA 传输。DMA 传输的数据 量是可编程的&#xff0c;最大达到 65535。每次传输之后相应的计数寄存器都做一次递减操作&#xff0c;直到 计数为&am…

jdk环境、tomcat环境

回顾复习 安装nodejs&#xff0c;和jdk一样的软件运行环境 yum -y list installed|grep epel #是否安装epel yum -y install nodejs node -v #查看版本号 下载对应的nodejs软件npm yum -y install npm npm -v #查 npm set config ....淘宝镜像 安装vue/cli…