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

news2024/11/24 9:09:15

接上文多数据源配置:

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-input
          v-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") {
        @Override
        protected 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);

        // 切换数据源 slave
        Console.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);

        // 切换数据源 slave
        Console.log("切换数据源:slave----------------------");
        String slave = DynamicDataSourceContextHolder.push("slave");
        Console.log("已经设置数据源,打印当前数据源:{}", slave);
        //调用完成:清空当前线程数据源
        DynamicDataSourceContextHolder.poll();

        // 切换数据源 master
         Console.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")
    @Transactional
    public void testDynamic7() {
        iTestDemoService.deleteIdMaster(2L);
        iTestDemoService.deleteIdSlave(2L);
    }



//实现类的调用
 @Override
    public 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")
    @DSTransactional
    public void testDynamic7() {
        iTestDemoService.deleteIdMaster(2L);
        iTestDemoService.deleteIdSlave(2L);
    }

结果:删除从库

总结:

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

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

六、拦截器切换数据源

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

配置类:

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

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

  1. 根据request请求判断
  2. 获取请求头参数切换
  3. 根据登录用户切换
@Slf4j
public class DynamicInterceptor implements HandlerInterceptor {

    //请求处理之前调用
    @Override
    public 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;
    }

    //请求处理但是页面未渲染调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    //请求处理完毕调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SPICE | 常见电路SPICE模型总结

Ref. 1. CMOS VLSI Design: A Circuits and Systems Perspective 目录 0 基础 1 反相器 inverter 2 缓存器 buffer 3 NAND 4 NOR 5 传输门 Transmission gate 6 三态反相器 Tristate Inverter 7 选择器 Multiplexers 8 D锁存器 D Latch 9 D触发器 D Flip-Flop 0 基础…

Linux文件描述符

前言 我们以前就听过"Linux下一切皆文件"&#xff0c;但是说实话我们只是记住了这句话&#xff0c;实质是不理解的&#xff01;本期我们就会解释&#xff01; 本期内容介绍 • 回顾C语言文件操作 • 系统I/O操作接口 • 文件描述符fd • 理解Linux下一切皆文件 • …

如何设置postgresql数据库的账户密码

说明&#xff1a;在我的云服务器上&#xff0c;postgres是使用yum的方式安装的&#xff0c;不需要设置postgres账户的密码&#xff0c;本文介绍安装后如何手动设置postgres账户的密码&#xff1b; postgres数据库安装&#xff0c;参考下面这篇文章&#xff1a; PostgreSQL安装…

构建基于Spring Boot的SaaS应用

引言 在设计和实现SaaS系统时&#xff0c;安全性是至关重要的考虑因素。一个全面的安全策略不仅能保护系统免受恶意攻击&#xff0c;还能确保用户数据的机密性、完整性和可用性。本文将探讨在SaaS架构中实现数据加密、敏感信息保护以及应用安全的最佳实践和技术方案&#xff0…

【大模型】基于LoRA微调Gemma大模型(1)

文章目录 一、LoRA工作原理1.1 基本原理1.2 实现步骤 二、LoRA 实现2.1 PEFT库&#xff1a;高效参数微调LoraConfig类&#xff1a;配置参数 2.2 TRL库SFTTrainer 类 三、代码实现3.1 核心代码3.2 完整代码 参考资料 大模型微调技术有很多&#xff0c;如P-Tuning、LoRA 等&#…

Vue3计算属性终极实战:可媲美Element Plus Tree组件研发之节点勾选

前面完成了JuanTree组件的节点编辑和保存功能后&#xff0c;我们把精力放到节点勾选功能实现上来。**注意&#xff0c;对于组件的开发者来说&#xff0c;要充分考虑用户的使用场景&#xff0c;组件提供的多个特性同时启用时必须要工作良好。**就拿Tree组件来说&#xff0c;用户…

数据库(MySQL)-视图、存储过程、触发器

一、视图 视图的定义、作用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表。但是视图只能用来查看表&#xff0c;不能做增删改查。 视图的作用&#xff1a;①简化查询 ②重写格式化数据 ③频繁访问数据库 ④过…

如何学习Doris:糙快猛的大数据之路(从入门到专家)

引言:大数据世界的新玩家 还记得我第一次听说"Doris"这个名字时的情景吗?那是在一个炎热的夏日午后,我正在办公室里为接下来的大数据项目发愁。作为一个刚刚跨行到大数据领域的新手,我感觉自己就像是被丢进了深海的小鱼—周围全是陌生的概念和技术。 就在这时,我的…

江苏科技大学24计算机考研数据速览,有专硕复试线大幅下降67分!

江苏科技大学&#xff08;Jiangsu University of Science and Technology&#xff09;&#xff0c;坐落在江苏省镇江市&#xff0c;是江苏省重点建设高校&#xff0c;江苏省人民政府与中国船舶集团有限公司共建高校&#xff0c;国家国防科技工业局与江苏省人民政府共建高校 &am…

pyqt designer使用spliter

1、在designer界面需要使用spliter需要父界面不使用布局&#xff0c;减需要分割两个模块选中&#xff0c;再点击spliter分割 2、在分割后&#xff0c;再对父界面进行布局设置 3、对于两边需要不等比列放置的&#xff0c;需要套一层 group box在最外层进行分割

Linux系统:date命令

1、命令详解&#xff1a; date 命令可以用来显示或设定系统的日期与时间。 2、官方参数&#xff1a; -d, --dateSTRING 通过字符串显示时间格式&#xff0c;字符串不能是now。-f, --fileDATEFILE 类似 --date 在 DATEFILE 的每一行生效-I[FMT], --iso-8601[FMT…

Redis的使用场景、持久化方式和集群模式

1. Redis的使用场景 热点数据的缓存 热点数据&#xff1a;频繁读取的数据 限时任务的操作。比如短信验证码 完成session共享的问题。因为前后端分离 完成分布式锁 商品的销售量 2. Redis的持久化方式 2.1 什么是持久化 把内存中的数据存储到磁盘的过程。同时也可以把磁盘中…

Python中的Numpy库使用方法

numpy Ndarry和创建数组的方式 NumPy数组&#xff08;ndarray&#xff09;是NumPy库的核心数据结构&#xff0c;它是一系列同类型数据的集合&#xff0c;以 0 下标为开始进行集合中元素的索引。 ndarray本质上是一个存放同类型元素的多维数组&#xff0c;其中的每个元素在内存…

TransformerEngine

文章目录 一、关于 TransformerEngine &#xff1f;亮点 二、使用示例PyTorchJAXFlax 三、安装先决条件Dockerpip从源码使用 FlashAttention-2 编译 四、突破性的变化v1.7: Padding mask definition for PyTorch 五、FP8 收敛六、集成七、其它贡献论文视频最新消息 一、关于 Tr…

美团大众点评字符验证码

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(…

为什么优秀员工往往最先离职?

在企业管理中有很多误区&#xff0c;令企业流失优秀员工和人才&#xff0c;根据优思学院过往的经验&#xff0c;大致可以分为以下几个情况。 1. 忽视帕累托法则&#xff08;80/20法则&#xff09; 帕累托法则&#xff08;80/20法则&#xff09;是六西格玛管理的基本原则&…

好的STEM编程语言有哪些?

STEM是科学&#xff08;Science&#xff09;&#xff0c;技术&#xff08;Technology&#xff09;&#xff0c;工程&#xff08;Engineering&#xff09;&#xff0c;数学&#xff08;Mathematics&#xff09;四门学科英文首字母的缩写&#xff0c;STEM教育简单来说就是在通过在…

django_创建菜单(实现整个项目的框架,调包)

文章目录 前言代码仓库地址在线演示网址启动网站的时候出现错误渲染路径的一些说明文件结构网页显示一条错误路由顺序js打包出现问题的代码函数没有起作用关于进度开发细节显示不了图片梳理一下函数调用的流程修改一些宽度参数classjs 里面的一些细节让三个按钮可以点击设置按钮…

前端JS特效第56集:基于canvas的粒子文字动画特效

基于canvas的粒子文字动画特效&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compat…

GPT-4O 的实时语音对话功能在处理多语言客户时有哪些优势?

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量 我瞄了一眼OpenAI春季发布会&#xff0c;这个发布会只有26分钟&#xff0c;你可以说它是一部科幻短片&#xff0c;也可以说它过于“夸夸其谈”&#xff01;关于…