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虚机安装
创建新的虚机: 选择安装软件(这里选的是桌面版,也可以根据实际情况进行选择) 等待检查软件依赖关系 选择安装位置,自主配置分区 创建一个普通用户 安装完成后重启 点击完成配置,进入登陆界面…...
终极音乐解锁方案:在浏览器中实现加密音乐文件高效转换完整指南
终极音乐解锁方案:在浏览器中实现加密音乐文件高效转换完整指南 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库: 1. https://github.com/unlock-music/unlock-music ;2. https://git.unlock-music.dev/um/web 项目地…...
FlexASIO:打破专业音频门槛,让普通设备也能拥有专业级ASIO体验
FlexASIO:打破专业音频门槛,让普通设备也能拥有专业级ASIO体验 【免费下载链接】FlexASIO A flexible universal ASIO driver that uses the PortAudio sound I/O library. Supports WASAPI (shared and exclusive), KS, DirectSound and MME. 项目地址…...
RMBG-2.0企业级应用:集成至Shopify后台实现订单图自动去背流水线
RMBG-2.0企业级应用:集成至Shopify后台实现订单图自动去背流水线 想象一下,你是一家Shopify店铺的运营负责人。每天,团队需要处理上百张来自不同供应商的商品图片,手动抠图、换背景,只为让商品主图在网站上看起来统一…...
终极免费抖音无水印视频下载完整教程:3步快速获取高清素材
终极免费抖音无水印视频下载完整教程:3步快速获取高清素材 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback s…...
终极揭秘:4步掌握Unity视觉还原技术核心
终极揭秘:4步掌握Unity视觉还原技术核心 【免费下载链接】UniversalUnityDemosaics A collection of universal demosaic BepInEx plugins for games made in Unity3D engine 项目地址: https://gitcode.com/gh_mirrors/un/UniversalUnityDemosaics Universa…...
需要控制重复点击按钮的通用方法
如图所示 在需要控制重复点击的地方使用通用方法去控制 省时省力 比用传统的分页定时器更方便...
QODER
...
在GCP上运行autoresearch
Andrej Karpathy最近开源了autoresearch,这是一个将真实LLM训练环境交给AI代理并让它自主实验的项目。代理修改模型代码,训练恰好5分钟,检查验证损失是否改善,保留或丢弃更改,然后重复。你去睡觉;醒来时会看…...
阿里千问,有个海外版
阿里千问,有个海外版。我也是最近才知道,用了一下,发现审核尺度明显要宽松很多,国内的千问明显被约束很多,就是个半残品。据说啊,国际版千问的部分数据放在了新加坡,对标的是ChatGPT。好像现在阿…...
【计算机网络工程论文】基于三层交换的局域网设计:连平中学教学楼VLAN划分与eNSP仿真应用
摘 要 随着连平中学发展和信息化平台的建设,面对庞大的信息数据和高要求的管理效率,网络的规划、管理、安全逐渐成为关键。对教学楼而言,规划一个高效、稳定、可扩展的局域网至关重要。 本文针对连平中学教学单位,鉴于其所有部门…...
