SpringBoot动态切换数据源
SpringBoot整合多数据源,动态添加新数据源并切换
- 1.需求
- 2.创建数据源配置类
- 3.切换数据源
- 4.切换数据源管理类
- 5.使用案例
- 5.AOP切面拦截
1.需求
低代码服务需要给多套系统进行功能配置,要求表结构必须生成在对应系统的数据库中,所以表结构的生成需要动态的获取目标系统的数据库信息,切换当前数据源到目标数据库,然后再生成。
2.创建数据源配置类
package com.tyq.datasource.config.datasource;import com.alibaba.druid.pool.DruidDataSource;
import com.tyq.datasource.config.Properties;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;/*** 向Spring容器中注入DruidConfiguration** @author 谭永强*/
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DruidConfiguration {@Resourceprivate Properties properties;/*** 数据源(默认)*/public DataSource dataSourceToDefault() {DruidDataSource datasource = new DruidDataSource();try {datasource.setQueryTimeout(0);datasource.setUrl("jdbc:oracle:thin:@192.168.0.30:1521/test2");datasource.setUsername("SCPS");datasource.setPassword("yldtscps");} catch (SQLException e) {e.printStackTrace();}return datasource;}@Bean@Primarypublic DataSource dataSource() {Map<Object, Object> dataSourceMap = new HashMap<>(2);dataSourceMap.put("default", dataSourceToDefault());DynamicDatasource dynamicDatasource = new DynamicDatasource();// 注入目标数据源,如果有多个数据源,直接加入map即可dynamicDatasource.setTargetDataSources(dataSourceMap);// 注入默认数据源dynamicDatasource.setDefaultTargetDataSource(dataSourceToDefault());return dynamicDatasource;}
}
3.切换数据源
SpringBoot动态切换数据源主要依靠AbstractRoutingDataSource类,这个抽象类中有一个属性为targetDataSources。该属性为Map结构,所有需要切换的数据源都存放在其中,根据指定的KEY进行切换。
在AbstractRoutingDataSource源码中,获取数据库连接是通过this.determineTargetDataSource().getConnection()去获取的,而this.determineTargetDataSource()方法中的DataSource则是通过determineCurrentLookupKey()方法返回的key值去前面的dynamicDatasource.setTargetDataSources(dataSourceMap);设置进去的map中查找的。
所以我们需要重写determineCurrentLookupKey()方法,如下:
package com.tyq.datasource.config.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 动态数据源** @author 谭永强* @date 2023-08-03*/
public class DynamicDatasource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDatasourceHolder.getDataSource();}
}
4.切换数据源管理类
数据源属于公共资源,考虑到多线程的情况下,我们将数据源存储在【ThreadLocal】中,保证线程隔离。
package com.tyq.datasource.config.datasource;import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;/*** 数据源切换管理** @author 谭永强* @date 2023-08-03*/
public class DynamicDatasourceHolder {/*** 保存数据源的映射*/private final static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();public static String getDataSource() {return CONTEXT_HOLDER.get();}public static void setDataSource(String dataSourceKey) {CONTEXT_HOLDER.set(dataSourceKey);}public static void removeDataSource() {CONTEXT_HOLDER.remove();}
}
5.使用案例
在Controller中通过DynamicDatasourceHolder指定当前需要使用哪个数据源,具体使用如下:
@RequestMapping("findById")
public User findById(String userId) {//指定数据源DynamicDatasourceHolder.setDataSource("default");if (ObjectUtils.isEmpty(userId)) {throw new ParamValidateException("userId不能为空");}User user = userService.findById(userId);//移除当前数据源DynamicDatasourceHolder.removeDataSource();return user;
}
上述案例中已经实现了如果动态切换数据源的过程,但是在实际开发中还是太繁琐,比如每个接口都必须添加相关切换数据源的代码,对代码的侵入性太高,下面我们通过AOP的形式去优化切换数据源的过程。
5.AOP切面拦截
通过AOP切面对方法的前置和后置做切换数据源的操作,这样就降低了与业务代码耦合、
<!--AOP-->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version>
</dependency>
注意:如果项目中无法导入@Aspect,则需要添加上述依赖。
package com.tyq.datasource.aop;import com.alibaba.druid.pool.DruidDataSource;
import com.tyq.datasource.config.Properties;
import com.tyq.datasource.config.datasource.DynamicDatasource;
import com.tyq.datasource.config.datasource.DynamicDatasourceHolder;
import com.tyq.datasource.config.exception.ParamValidateException;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;/*** 动态数据源切换AOP** @author 谭永强* @date 2023-08-03*/
@Aspect
@Component
public class DynamicDatasourceAop {@Autowiredprotected ApplicationContext applicationContext;@Resourceprivate Properties properties;/*** 定义切点*/@Pointcut("execution (* com.tyq.datasource.controller.*.*(..))")public void pointcut() {}/*** 前置处理*/@Before(value = "pointcut()")public void beforeAdvice() {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (ObjectUtils.isEmpty(attributes)) {return;}HttpServletRequest request = attributes.getRequest();String dataSourceKey = request.getParameter("dataSourceKey");if (ObjectUtils.isEmpty(dataSourceKey)) {throw new ParamValidateException("dataSourceKey不能为空!");}//获取当前动态数据源DynamicDatasource dynamicDatasource = applicationContext.getBean(DynamicDatasource.class);//所有已连接的数据源集合Map<Object, DataSource> resolvedDataSources = dynamicDatasource.getResolvedDataSources();if (ObjectUtils.isEmpty(resolvedDataSources.get(dataSourceKey))) {//此处为模拟到数据库中查询数据源信息的过程,查询到数据源信息后,创建数据源并添加到动态数据源管理中//动态加入新的数据源DataSource dataSource = getDataSource();Map<Object, Object> dataSourceMap = new HashMap<>(resolvedDataSources.size() + 1);dataSourceMap.putAll(resolvedDataSources);dataSourceMap.put("three", dataSource);dynamicDatasource.setTargetDataSources(dataSourceMap);}//动态切换数据源DynamicDatasourceHolder.setDataSource(dataSourceKey);}/*** 后置处理*/@AfterReturning(value = "pointcut()")public void afterAdvice() {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();if (ObjectUtils.isEmpty(attributes)) {return;}HttpServletRequest request = attributes.getRequest();String dataSourceKey = request.getParameter("dataSourceKey");if (ObjectUtils.isEmpty(dataSourceKey)) {throw new ParamValidateException("dataSourceKey不能为空!");}System.out.println("后置处理:" + dataSourceKey);//删除当前数据源DynamicDatasourceHolder.removeDataSource();}/*** 模拟成功查询到数据源并创建DataSource** @return 数据源*/public DataSource getDataSource() {DruidDataSource datasource = new DruidDataSource();try {datasource.setQueryTimeout(0);datasource.setUrl("jdbc:oracle:thin:@192.168.0.59:1526/qstest");datasource.setUsername("SCPS");datasource.setPassword("yldtscps");} catch (SQLException e) {e.printStackTrace();}return datasource;}
}相关文章:
SpringBoot动态切换数据源
SpringBoot整合多数据源,动态添加新数据源并切换 1.需求2.创建数据源配置类3.切换数据源4.切换数据源管理类5.使用案例5.AOP切面拦截 1.需求 低代码服务需要给多套系统进行功能配置,要求表结构必须生成在对应系统的数据库中,所以表结构的生成…...
[C++项目] Boost文档 站内搜索引擎(4): 搜索的相关接口的实现、线程安全的单例index接口、cppjieba分词库的使用、综合调试...
有关Boost文档搜索引擎的项目的前三篇文章, 已经分别介绍分析了: 项目背景: 🫦[C项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍…文档解析、处理模块parser的实现: 🫦[C项目] Boost文档 站内搜索引擎(2): 文档文本解析模块…...
SAP ABAP元素域值描述通过函数(DD_DOMVALUE_TEXT_GET)获取
代码如下: PERFORM FRM_GET_DOMVALUE_TEXT USING ZMMD_ZFLZQ <GFS_DATA>-ZFLZQ CHANGING <GFS_DATA>-ZZQTEXT .IF <GFS_DATA>-ZXYLX IS NOT INITIAL .PERFORM FRM_GET_DOMVALUE_TEXT USING ZMMD_ZXYLX <GFS_DATA>-ZXYLX CHANGING <GFS_…...
原型模式与享元模式:提升系统性能的利器
原型模式和享元模式,前者是在创建多个实例时,对创建过程的性能进行调优;后者是用减 少创建实例的方式,来调优系统性能。这么看,你会不会觉得两个模式有点相互矛盾呢? 在有些场景下,我们需要重复…...
uniapp封装手写签名
组件代码 cat-signature <template><view v-if"visibleSync" class"cat-signature" :class"{visible:show}" touchmove.stop.prevent"moveHandle"><view class"mask" tap"close" /><view c…...
掌握 JVM 调优命令
常用命令 1、jps查看当前 java 进程2、jinfo实时查看和调整 JVM 配置参数3、jstat查看虚拟机统计信息4、jstack查看线程堆栈信息5、jmap查看堆内存的快照信息 JVM 日常调优总结起来就是:首先通过 jps 命令查看当前进程,然后根据 pid 通过 jinfo 命令查看…...
扩增子分析流程——Lotus2: 一行命令完成所有分析
为什么介绍lotus2 因为快,作者比较了lotus2流程和qiime2、dada2、vsearch等,lotus2的速度最快、占用内存最小。 因为方便,只需要一行代码,即可完成全部分析。 lotus2 -i Example/ -m Example/miSeqMap.sm.txt -o myTestRun而且分…...
微服务 云原生:搭建 Harbor 私有镜像仓库
Harbor官网 写在文前: 本文中用到机器均为虚拟机 CentOS-7-x86_64-Minimal-2009 镜像。 基础设施要求 虚拟机配置达到最低要求即可,本次系统中使用 docker 24.0.4、docker-compose 1.29.2。docker 及 docker-compose 的安装可以参考上篇文章 微服务 &am…...
Ceph入门到精通-远程开发Windows下使用SSH密钥实现免密登陆Linux服务器
工具: win10、WinSCP 服务器生成ssh密钥: 打开终端,使账号密码登录,输入命令 ssh-keygen -t rsa Winscp下载 Downloading WinSCP-6.1.1-Setup.exe :: WinSCP window 生成密钥 打开powershell ssh-keygen -t rsa 注意路径 …...
APP外包开发的开发语言对比
在开发iOS APP时有两种语言可以选择,Swift(Swift Programming Language)和 Objective-C(Objective-C Programming Language),它们是两种不同的编程语言,都被用于iOS和macOS等苹果平台的软件开发…...
基于Python++PyQt5马尔科夫模型的智能AI即兴作曲—深度学习算法应用(含全部工程源码+测试数据)
目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境PC环境配置 模块实现1. 钢琴伴奏制作1)和弦的实现2)和弦级数转为当前调式音阶3)根据预置节奏生成伴奏 2. 乐句生成1)添加音符2)旋律生成3)节…...
Android中简单封装Livedata工具类
Android中简单封装Livedata工具类 前言: 之前讲解过livedata和viewmodel的简单使用,也封装过room工具类,本文是对livedata的简单封装和使用,先是封装了一个简单的工具类,然后实现了一个倒计时工具类的封装. 1.LiveD…...
国内大模型在局部能力上已超ChatGPT
中文大模型正在后来居上,也必须后来居上。 数科星球原创 作者丨苑晶 编辑丨大兔 从GPT3.5彻底出圈后,大模型的影响力开始蜚声国际。一段时间内,国内科技公司可谓被ChatGPT按在地上打,毫无还手之力。 彼时,很多企业…...
监控设置ip地址怎么设置
监控设备的IP地址设置是保障监控系统正常工作的基础。通过设置IP地址,我们可以确定监控设备在局域网内的位置,并远程访问监控设备进行实时查看、存储视频等操作。下面虎观代理小二二将介绍具体步骤。 方法一: 和电脑连接在一起,…...
力扣:56. 合并区间(Python3)
题目: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 来源:力扣(Lee…...
最小二乘问题和非线性优化
最小二乘问题和非线性优化 0.引言1.最小二乘问题2.迭代下降法3.最速下降法4.牛顿法5.阻尼法6.高斯牛顿(GN)法7.莱文贝格马夸特(LM)法8.鲁棒核函数 0.引言 转载自此处,修正了一点小错误。 1.最小二乘问题 在求解 SLAM 中的最优状态估计问题时,我们一般…...
Selenium/webdriver原理解析
最近在看一些底层的东西。driver翻译过来是驱动,司机的意思。如果将webdriver比做成司机,竟然非常恰当。 我们可以把WebDriver驱动浏览器类比成出租车司机开出租车。在开出租车时有三个角色: 乘客:他/她告诉出租车司机去哪里&…...
多用户跨境B2B2C商城后台管理系统快速搭建
搭建一个多用户跨境B2B2C商城后台管理系统需要考虑多个方面,包括系统架构设计、用户权限管理、商品管理、订单管理、支付管理、物流管理等。搭建步骤如下: 1. 系统架构设计 首先,需要设计一个稳定可靠的系统架构。选择一个适合B2B2C商城的商…...
MySQL 优化
问题描述 MySQL 的性能优化分为四个部分: 硬件和操作系统层面的优化架构设计层面的优化MySQL 程序配置优SQL 优化 一、硬件及操作系统层面优化 从硬件层面来说,影响 Mysql 性能的因素有,CPU、可用内存大小、磁盘读写速度、 网络带宽。 从操作…...
VMware Workstation及CentOS-7虚机安装
创建新的虚机: 选择安装软件(这里选的是桌面版,也可以根据实际情况进行选择) 等待检查软件依赖关系 选择安装位置,自主配置分区 创建一个普通用户 安装完成后重启 点击完成配置,进入登陆界面…...
UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
