浅谈 Mybatis 动态数据源切换是如何实现的

news/2024/4/26 7:52:54/文章来源:https://blog.csdn.net/qq_42875345/article/details/126250844

前言

小憩是辣么的让人神往,就像备战高考靠窗位置的那个你,肆无忌道的放空自己,望着深蓝色宁静的天空,思考着未来该何去何从,近处一颗高大魁梧的银杏树在炎炎夏日中尽情的摇曳着自己嫩绿的枝丫,迸发出无尽的希望,回想起来一切都是这么的美好。好了今日的杂想到此结束,回归正文,关于动态数据源切换那点事。

引导思考

如果咱们现在生活在互联网刚开始兴起的那个时期,万物堵塞,所有和数据库打交道的操作只能通过 JDBC 来实现,恰巧你是一个技术狂热爱好者,想通过自己的努力封装一套半 ORM 框架,造福千千万程序员,你会如何实现动态数据源切换这个扩展点呢?

  1. 当用户没有动态数据源切换的需求时:框架加载默认数据源给用户使用
  2. 当用户有动态数据源切换的需求时:提供一个官方认证的工具给用户使用,用户只需按照要求将多数据源配置好,系统会加载用户自定义的数据源

对于点一很好办:我们在框架内部默认创建一个名字为 dataSource(Spring Boot 默认数据源:HikariDataSource)、类型为 javax.sql.DataSource 的这么一个 数据源就好了。
对于点二来说:这个工具应该让用户的学习使用成本越低越好。从用户角度上分析,应该没有人希望把多数据源配置配在 Excel、Txt文件中吧,最优解配在 yml 文件中,然后利用 Spring 中的 Environment 类可以很容易加载到多数据源的配置。(题外话:基于 Spring 生态开发,少走弯路 30 年),到底最后是选择使用哪个数据源,设计思路有俩种:

  1. 可以支持加载多个数据源到系统中,写一个抽象类 AbstractDataSourceRoute 里面实现了 DataSource choiceDataSource(String type),将到底是使用哪个数据源的方法交给子类去实现 abstract String type(),这样用户只需实现 AbstractDataSourceRoute 类,指定一下使用哪个数据源,就可以了。伪代码如下
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;import javax.sql.DataSource;
import java.util.Map;public abstract class DataSourceUtil implements DataSource, ApplicationContextAware, DisposableBean {private static ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}DataSource choiceDataSource() {//1:通过 applicationContext 获取所有数据源Map<String, DataSource> dataSourceMap = applicationContext.getBeansOfType(DataSource.class);//2:通过 type 筛选指定要用的数据源DataSource dataSource = dataSourceMap.get(type());//3:返回用户指定要使用的数据源return dataSource;}abstract String type();
}

2: 限制系统加载一个数据源,扫描到自定义数据源,就不加载默认数据源,反之加载默认数据源

动态数据源切换之应用

先来简单介绍一下啊动态数据源用到的开发场景:

  1. 项目需要与其他系统对接,库是别人,因此需要配置多套数据源
  2. 项目本身根据实际需求设计数据库的时候,用到了多种的数据库,例如 Mysql、Oracle同时使用,当然数据互通是个问题

使用场景介绍完了,接下来简单介绍一下如何使用吧~,就是读取 yml 配置中我们配置的数据源属性,利用@ConfigurationProperties注解完成属性的自动填充,继而注入到 IOC 容器中。这时候系统读取到了多个数据源,但是还不清楚什么时候用哪个数据源呢,因此我们可以编写一个切面,来动态的告知系统该如何选择数据源

@Configuration
public class DataSourceConfig {@Bean(name = "master")@ConfigurationProperties(prefix = "spring.datasource.druid.master.datasource")public DataSource master() {return new DruidDataSource();}@Bean(name = "slave")@ConfigurationProperties(prefix = "spring.datasource.druid.salve.datasource")public DataSource slave() {return new DruidDataSource();}@Bean@Primarypublic DynamicDataSource multipleDataSource(@Qualifier("master") DataSource db1, @Qualifier("slave") DataSource db2) {DynamicDataSource multipleDataSource = new DynamicDataSource();Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceType.getMASTER(), db1);targetDataSources.put(DataSourceType.getSALVE(), db2);multipleDataSource.setTargetDataSources(targetDataSources);multipleDataSource.setDefaultTargetDataSource(db1);DynamicDataSourceContextHolder.dataSourceIds.add(DataSourceType.getSALVE());DynamicDataSourceContextHolder.dataSourceIds.add(DataSourceType.getMASTER());return multipleDataSource;}@Datastatic class DataSourceType {private static String MASTER = "master";private static String SALVE = "salve";public static String getMASTER() {return MASTER;}public static String getSALVE() {return SALVE;}}
}

Aspect 切面

编写自定义注解 TargetDataSource ,并通过切面拦截它,根据 TargetDataSource 中的属性做判断,选择特定的数据源。里面的逻辑没啥好看的,就是在方法执行之前,获取方法或者类上面 TargetDataSource 中指定的属性,填充到 DynamicDataSourceContextHolder 中,后续我们重写 Mybatis 为我们提供的数据源选择钩子方法,返回 DynamicDataSourceContextHolder 中的数据就好了。这样一来就实现了,数据源切换的需求了,接下来看下这个钩子方法里面的源码吧~~

@Slf4j
@Aspect
@Order(-10)
@Component
public class AspectWithinAnnotation {/*** @within:拦截类上的注解* @annotation:拦截方法上的注解*/@Before("@within(com.example.oraceldemo.config.aspect.TargetDataSource)||@annotation(com.example.oraceldemo.config.aspect.TargetDataSource)")public void changeDataSource(JoinPoint joinPoint) {TargetDataSource targetDataSource = getTargetDataSource(joinPoint);if (targetDataSource == null || !DynamicDataSourceContextHolder.isContainsDataSource(targetDataSource.name())) {log.error("使用默认的数据源 -> " + joinPoint.getSignature());} else {log.debug("使用数据源:" + targetDataSource.name());DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.name());}}@After("@within(com.example.oraceldemo.config.aspect.TargetDataSource)||@annotation(com.example.oraceldemo.config.aspect.TargetDataSource)")public void clearDataSource(JoinPoint joinPoint) {log.debug("清除数据源 " + getTargetDataSource(joinPoint).name() + " !");DynamicDataSourceContextHolder.clearDataSourceType();}/*** 先从方法上获取 TargetDataSource 注解,获取不到从类上面获取*/public TargetDataSource getTargetDataSource(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();Method method = methodSignature.getMethod();TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);if (targetDataSource == null) {Class<?> declaringClass = method.getDeclaringClass();targetDataSource = declaringClass.getAnnotation(TargetDataSource.class);}return targetDataSource;}
}

Mybatis 扩展点之 AbstractRoutingDataSource

又是这个 Abstract 开头的类~,记住所有以 Abstract 开头的类,他的父类才是真正干活的人,因为详细的逻辑都封装在父类中了,爸爸才是全家的顶梁柱、主心骨啊,所有的风雨、压力都是爸爸来抗,只为了子女脸上洋溢着的灿烂的笑容。

public class DynamicDataSource extends AbstractRoutingDataSource {/*** 选择数据源钩子方法*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {super.setDefaultTargetDataSource(defaultTargetDataSource);super.setTargetDataSources(targetDataSources);super.afterPropertiesSet();}public DynamicDataSource() {}
}

浅浅的 debug 一下源码吧

在钩子方法中打一个断点,看一下它的执行链路,一次往上翻就好了,来到 AbstractRoutingDataSource 中的 determineTargetDataSource 方法,粗略的一看,这代码和本文开头引导思考中的伪代码,不就是一个模子里刻出来的咩~由于代码思路都是一样的,就不过多的做分析了。为了读者方便理解再次贴一遍原话如下~

写一个抽象类 AbstractDataSourceRoute 里面实现了 DataSource choiceDataSource(String type),将到底是使用哪个数据源的方法交给子类去实现 abstract String type(),这样用户只需实现 AbstractDataSourceRoute 类中的钩子方法,指定一下使用哪个数据源,就可以了。

在这里插入图片描述

浅浅的解读一下 resolvedDataSources 吧

上图一从 resolvedDataSources 中根据钩子方法的返回值,获取指定的数据源返回,那么 resolvedDataSources 中的数据源是什么时候注入的呢?答案入下图,利用了 Spring 中的 InitializingBean 接口,在Bean属性填充完毕后,将 targetDataSources 中数据源全部放到 resolvedDataSources 中,这样一来,我们用户只需指定指定钩子方法中的数据源类别,当方法被调用的时候,切面就会截取方法、类上面的自定义注解,填充到 ThreadLocal 中,然后后续的 Mybatis 获取数据源查 DB 的时候,根据钩子方法的返回值,从 resolvedDataSources 中获取指定的数据源然后查 DB 从而实现了,数据源随意切换的效果
在这里插入图片描述

注意点

由于本文中的切面拦截的是自定义注解,且切入点是使用 @within、@annotation来进行修饰的(即只会识别被调用方法对应的类上、或者是被调用方法上是否存在自定义注解),考虑到现在大多数人都在用 MybatisPlus~,正确用法入下图一、二,图三为错误用法,因为,我们调用的 List 方法本质还是 ServiceImpl 类上的,然而ServiceImpl 类上没有有被我们的注解修饰,故此时切面会失效,本文切面解析注解的范围如下图四。当然可能还有人切入点采用 @Before(“execution(* com.example.oraceldemo.service..(…))”) ,也可能出现切面失效、切换数据源失效的情况,大致的分析过程差不多,读者有兴趣可以自行去分析哦~~~
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

@Autowired
private TabStarService tabStarService;@Test
void xiaomi() {List<Goods> list = goodsService.list();System.err.println(list);
}

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

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

相关文章

计算机毕业设计ssm+vue基本微信小程序的高速公路服务区充电桩在线预订系统 uniapp 小程序

项目介绍 随着网络技术的发展,当前人们的生活模式发生了巨大的变化,特别是以电子商务为代表的产业影响了人们的生活。当前,电子商务成为振兴国家经济的重要手段,电子商务为人们的生活提供了极大的便利,帮助企业降低销售成本,提高销售效率。高速公路服务区作为传统的实体行业,经…

BGP BFD测试案例

一、BFD原理 1.1 BFD技术简介 一种全网统一、检测迅速、监控网络中链路或者IP路由的双向转发连通状况&#xff0c;并未上层应用提供服务的技术。 1.2 BFD会话建立方式和监测机制 ●BFD的标识符&#xff1a; &#xff08;1&#xff09;BFD建立会话存在标识符的概念&#xff…

中小企业数字化思考:数字化转型应该走自己的路

随着数字化的发展&#xff0c;以及数字中国概念的形成&#xff0c;和以前国央企宣布数字化转型时的不同&#xff0c;现在越来越多的企业开始寻求数字化转型&#xff0c;促使自身业务能够更好的发展。现在看过去&#xff0c;各行各业都有大量企业进行了数字化转型规划&#xff0…

【Mac】VSCode 更新1.73版本后JSTS代码跳转异常

前言 今天有小伙伴MacOS更新了VS Code版本后&#xff0c;说工程内的代码跳转全部异常了&#xff0c;没法正确跳转。搞了两三个小时没搞出来&#xff0c;找到了我&#xff0c;让我帮忙瞧瞧。排查下来发现这问题有点意思&#xff0c;故此记录一下。 问题 排查姿势 1. 提示没有定…

Skywalking9.2.0监控浏览器

Skywalking9.2.0监控浏览器 安装skywalking-client-js npm install skywalking-client-js --save在main.js添加信息 import ClientMonitor from skywalking-client-jsrouter.afterEach(() > {ClientMonitor.setPerformance({service: 服务名,serviceVersion: 版本号,pagePat…

基于模糊小波神经网络的空中目标威胁评估(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 在现代战争中, 随着信息化和智能化的飞速发展, 以及作战环境的日益复杂, 实时而准确地评估目标威胁, 不仅为空战决策提供科学的…

程序人生:技术水平低,就这还敢写自动化项目实战经验丰富?

今年部门要招两个自动化测试&#xff0c;这几个月我面试了几十位候选人。发现一个很奇怪的现象&#xff0c;面试中一问到元素定位、框架api、脚本编写之类的&#xff0c;很多候选人都对答如流。但是一问到实际项目&#xff0c;比如 “如何从0开始搭建自动化体系”、“如果让你来…

资深大牛纯手写RabbitMQ 核心笔记,还有谁?

RabbitMQ简介 RabbitMQ是消息代理(Message Broker)&#xff0c;它支持多种异步消息处理方式&#xff0c;最常见的有&#xff1a; Work Queue&#xff1a;将消息缓存到一个队列&#xff0c;默认情况下&#xff0c;多个worker按照Round Robin的方式处理队列中的消息。每个消息只…

CART回归树算法

【题目1】 表1为拖欠贷款人员训练样本数据集,使用CART算法基于该表数据构造决策树模型,并使用表2中测试样本集确定剪枝后的最优子树。 表1 拖欠贷款人员训练样本数据集编号 房产状况 婚姻情况 年收(千元) 拖欠贷款1 是 单身 125 否2 否 已婚 100 否3 否 单身 70 否4 是 已婚…

一本通1064;奥运奖牌计数

#include <iostream> using namespace std; int main() {int n, Jin, Yin, Tong;int JinSum 0, YinSum 0, TongSum 0, sum;cin >> n;for (int i 1; i < n; i) // 循环n次{cin >> Jin >> Yin >> Tong; // 输入一天获得的金银铜牌数JinSum …

IR信息检索前沿梳理

1. 检索预训练 1.1 PROP: Pre-training with Representative Words Prediction for Ad-hoc Retrieval three types of pre-training tasks have been proposed including: Inverse Cloze Task (ICT): The query is a sentence randomly drawn from the passage and the docu…

全志F1C芯片参数对比,供查阅

F1C600特性介绍 组合32M DDR1&#xff0c;QFN编解码模式&#xff0c;生产音频核心板&#xff08;CPUNORWIFI&#xff09;在WIFI站下播放的功率约0.5W组合I2S、SPDIF、CODEC等多功能接口支持全格式音频解码芯片 F1C600参数介绍 中央处理器 ARM926EJ-S 内存 SIP DDR1 SD2.0…

月入18000,0基础转行软件测试,实现薪资翻倍我只用了135天

在没做测试之前&#xff0c;我一直是个没自信的人&#xff0c;因为工作不稳定&#xff0c;收入也不高。 大学毕业做了2年酒店管理&#xff0c;月入4000提成&#xff0c;还经常上夜班&#xff0c;熬人又伤身体&#xff0c;于是不想再做服务行业&#xff0c;就转行做了电销。这之…

本地数据库IndexedDB - 学员管理系统之列表管理(二)

IndexedDB是浏览器提供的本地数据库&#xff0c;它可以被网页脚本创建和操作。IndexedDB允许存储大量数据&#xff0c;提供查找接口&#xff0c;还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言&#xff0c;IndexedDB不属于关系型数据库&#xff08;不支…

使用VMware16克隆功能快速准备CentOS 7.9操作系统集群

记录&#xff1a;305 场景&#xff1a;使用VMware16克隆功能快速准备CentOS 7.9操作系统集群&#xff0c;主要内容&#xff1a;VMware16克隆功能功能使用、CentOS 7.9操作系统常用指令使用、制作本地yum源、安装JDK、配置集群NTP时间同步等。 版本&#xff1a; 虚拟机工具&a…

数据结构-难点突破(C++/Java详解实现串匹配算法KMP,next数组求法,KMP算法优化nextval数组)

文章目录1. 暴力匹配算法BF2. KMP算法next数组求法Java代码&#xff1a;C代码&#xff1a;KMP算法优化nextval数组1. 暴力匹配算法BF 在了解KMP算法前&#xff0c;就必须介绍串的暴力匹配算法&#xff08;BF算法&#xff09; BF算法&#xff0c;即暴力(Brute Force)算法&…

大赛征集令|首届“万应杯”低代码应用开发大赛报名开启啦!

探索&#xff0c;寻觅低码边界。 创新&#xff0c;做成未曾有人做过的事。 首届“万应杯”低代码应用开发大赛 报名正式启动啦&#xff01; 万元现金奖杯/证书项目转售收益 丰厚奖励&#xff0c;邀你来战&#xff01; 大赛时间 低码掘金&#xff0c;就在此时&#xff01; …

MySQL高级SQL语句(一)

MySQL高级SQL语句&#xff08;一&#xff09;MySQL高级SQL语句&#xff08;一&#xff09;一、高级SQL语句&#xff08;进阶查询&#xff09;1.1 select1.2 distinct1.3 where1.4 and 、or1.5 in1.6 between1.7 通配符1.8 like1.9 order by二、函数2.1 数学函数2.2 聚合函数2.3…

MSDC 4.3 接口规范(26)

MSDC 4.3 接口规范&#xff08;26&#xff09;7.4 组呼业务管理7.4.1 服务状态7.4.2 启动组呼业务7.4.2.1 接口函数7.4.2.2 先决条件7.4.2.3 说明7.4.2.4 调用流程7.4.2.4.1 启动组呼业务7.4.2.4.2 无法启动服务7.4.3 停止组呼服务7.4.3.1 接口函数7.4.3.2 先决条件7.4.3.3 说明…