MyBatis Plus 插件 动态数据源实现原理与源码讲解 (dynamic-datasource-spring-boot-starter-master)

news/2025/2/12 18:34:36/

目录

1. 介绍

2. 基本原理

3. 源码介绍

3.1 使用 AOP 拦截,方法执行前获取到当前方法要用的数据源

3.2 实现自定义 DataSource 接口,实现 DataSource 接口的 getConnect 方法做动态处理


1. 介绍

多数据源即一个项目中同时存在多个不同的数据库连接池。

比如 127.0.0.1:3306/test   127.0.0.1:3307/test 127.0.0.1:3308/test

总之项目存在需要操作多个库的需求。

具体在编码方面呢,具体就是一个service 中,方法1使用库1查询,方法2使用库2查询。

2. 基本原理

多数据源实现原理是什么呢?可分为两大关键部分

1. 使用 AOP 拦截,方法执行前获取到当前方法要用的数据源

可以使用自定义注解实现,注解参数带数据源名称,然后自己解析

2. 实现自定义 DataSource 接口,实现 DataSource 接口的 getConnect 方法做动态处理

动态处理,就是拿到 AOP 那一步获取到的数据源,直接返回该数据源

基本原理,可看这个简易图

3. 源码介绍

源码地址 https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

(源码一定要自己完整看一遍,此篇博客只展示部分关键源码)

3.1 使用 AOP 拦截,方法执行前获取到当前方法要用的数据源

@DS 注解代表定义当前方法、当前类使用哪个数据源

 value 指定当前类、方法使用的数据源名称

数据源名称也是在配置文件中定义的 

注解处理切面 DynamicDataSourceAnnotationAdvisor

切面 advice 由外部传过来,要处理的注解也从外面传过来。

也就是这里,这行代码的意思是

DynamicDataSourceAnnotationInterceptor 负责处理 DS 注解

 接着看 DynamicDataSourceAnnotationInterceptor 如何处理

下面分别解释下

1. 将 @DS 注解的 value 值压入 ThreadLocal 当前线程的栈

看这段方法 DynamicDataSourceContextHolder.push(dsKey);

ThreadLocal 中存储的是 Deque 类,也就是一个双端队列(两头都可以插入的队列) ,使用的是 ArrayDeque 双端队列,内部是一个数组。

为什么使用队列,而不是简单一个字符串,注释已经写的很清楚了,看注释即可。

 ArrayDeque 的 push 就是在队列首部添加一个元素。

2. 调用实际的方法

这里不是切面吗,实际方法也就是被拦截的方法。也就是直接调用业务逻辑。

3. 将第 1 步中的元素弹出来

业务逻辑执行完成后,就将刚才加入的元素弹出来。

其实这里很像 JVM 虚拟机栈,方法调用就是压入栈,方法结束调用就是出栈,栈顶就是当前执行的方法。

而此处这里的栈顶就代表 当前正在执行的方法所用的 数据源名称。而方法执行完了,这里也该出栈了。

这里核心逻辑已经完了,本质就是这么简单。

3.2 实现自定义 DataSource 接口,实现 DataSource 接口的 getConnect 方法做动态处理

DynamicRoutingDataSource 代表动态路由数据源

做的事情就是运行时动态路由出一个当前需要的数据源。

接着看源码,首先与 SpringBoot 整合时 自动配置出当前 Bean

DynamicRoutingDataSource 类图

3.2.1 DataSource

代表一个数据源,由javax 扩展定义

3.2.2 AbstractDataSource

抽象实现,将一些对数据源的配置操作都实现为不支持操作抛出异常 UnsupportedOperationException

(动态数据源相当于一个代理,不需要给动态数据源本身设置相关配置)

3.2.3 AbstractRoutingDataSource

抽象实现,路由动态配置源,实现了关键方法 getConnection ,完成了路由操作

看看源码  getConnection()

getConnection() 何时调用呢,也就是上一步的切面中的第 2 步中,invocation.proceed(),执行业务逻辑的过程中,遇到的 数据库层的操作时,就会到这里了。

这里直接看简单的非事务的获取数据源这里。

关键代码 determineDataSource().getConnection()

这个方法由子类实现,也就是下面的 DynamicRoutingDataSource

3.2.4 DynamicRoutingDataSource

动态路由数据源核心实现,完成 数据源的维护(添加删除数据源)数据源的选择

接着上面的源码流程,子类的 determineDataSource 方法最终调用了 getDataSource

 getDataSource 源码如下

/*** 获取数据源** @param ds 数据源名称* @return 数据源*/
public DataSource getDataSource(String ds) {if (StringUtils.isEmpty(ds)) {// 没有指定数据源名称,直接使用默认的数据源return determinePrimaryDataSource();} else if (!groupDataSources.isEmpty() && groupDataSources.containsKey(ds)) {// 从分组数据源中找一个数据源return groupDataSources.get(ds).determineDataSource();} else if (dataSourceMap.containsKey(ds)) {// 直接根据名称找一个数据源return dataSourceMap.get(ds);}if (strict) {// 开启了严格模式时,如果没有找到数据源,就抛出异常throw new CannotFindDataSourceException("dynamic-datasource could not find a datasource named" + ds);}// 使用默认的数据源return determinePrimaryDataSource();
}

下面分别讲解关键之处

1. 没有指定数据源名称,直接使用默认的数据源

没有指定代表的是没有加 @DS 注解,或者加了注解,但是 value 值没有写

此时就是用默认的数据源,默认的数据源是什么呢?

也就是配置文件中的 primary 中指定的数据源名称,如果不配置的话默认值就是 master

2. 实现自定义 DataSource 接口,实现 DataSource 接口的 getConnect 方法做动态处理

从分组找一个数据源 groupDataSources.get(ds).determineDataSource();

分组是什么意思?

分组定义的规则是 group_xxx,也就是数据源名称以下划线分割,下划线前面的就是组名。

分组的作用是什么呢?本质用于实现一个名称对应多数据库源。

比如一主多从,可以将从数据源都分到 slave 组里面,用的时候就是 @DS("slave") // 组名

在实际决定数据源的时候,就会按照一定的策略从这个组里的数据源挑选一个了。

接着看源码,如何 从分组数据源中找一个数据源

groupDataSources.get(ds).determineDataSource();

 最后到了策略的选择,DynamicDataSourceStrategy

DynamicDataSourceStrategy 有两个实现类

LoadBalanceDynamicDataSourceStrategy 负载均衡动态数据源策略

看源码,这个就是按顺序一个个选择下来,达到负载均衡方式

RandomDynamicDataSourceStrategy 随机动态数据源策略

这个完全就是纯随机选一个

3. 直接根据名称找一个数据源 

如果走到了这里,说明这个数据源名称没有配置分组,那就直接根据名称取这单个数据源了

直接纯 get 了

数据源何时初始化的

还是在 DynamicRoutingDataSource,这个类实现了 Spring InitializingBean 

接口回调方法 afterPropertiesSet,当当前 Bean 内部的属性都初始化完毕了后就回调这个方法

 看看  afterPropertiesSet 回调方法内容

 这里只看关键代码

1. dataSources.putAll(provider.loadDataSources());

@Autowired private List<DynamicDataSourceProvider> providers; providers 是什么呢 ? 

providers 代表 动态数据源配置的来源,默认实现就是从 yml 中来,也就是 SpringBoot 的 application.yml 配置

默认实现

传进去的参数配置类

DynamicDataSourceProvider 也就是解析了这些配置 来获取到所有配置

拿到配置后,就要解析这些配置了 ,这里委托了父类处理

这里完成创建数据源,然后将结果封装成了 Map<String, DataSource> dataSourceMap 返回

(泛型为 <数据源名称,数据源实例>)

 看看如何创建数据源的 defaultDataSourceCreator.createDataSource(dataSourceProperty)

大致流程如下:

这里介绍一下 creators

dynamic-datasource-creator 模块下定义了单独数据源创建的代码

DataSourceCreator 代表一个数据源创建器,用于创建一个数据源。

 每种数据源类型都有自己的创建器,比如这里常见的 Druid、Hikar

这里就举例其中一个 HikariDataSourceCreator,其他的都差不多

HikariDataSourceCreator

调用这些创造器的创建的时候默认直接就启动了,除非配置了懒加载。

到现在,数据源就已经创建完了。再次说一下这是在Spring 的 afterPropertiesSet 回调里完成创建的。(afterPropertiesSet  即当前 Bean 的所有属性 Spring 都填充完毕后回调)

2. addDataSource(dsItem.getKey(), dsItem.getValue());

上一步的 provider.loadDataSources() 讲解完毕了,这次看看下面的 addDataSource(dsItem.getKey(), dsItem.getValue());

 addDataSource 方法

 首先是先给 dataSourceMap 放进去了。这里会返回旧的数据源(如果是第一次加入,则返回null),所以下面判断了如果返回有值旧关闭掉旧的数据源,关闭就是调用数据源的 close 方法。

然后是 addGroupDataSource

这里数据源就完成了添加,这个整体步骤都是在启动的时候添加的, 后面的 getConnect 方法都只是获取了。 

最后再放这张图简单总结下。


http://www.ppmy.cn/news/74035.html

相关文章

springboot+jsp+java流浪动物猫狗领养救助网站367hp

本流浪猫狗领养救助网站共包含14个表:分别是宠物类型信息表&#xff0c;配置文件信息表&#xff0c;流浪宠物评论表信息表&#xff0c;活动类型信息表&#xff0c;领养宠物信息表&#xff0c;领养中心信息表&#xff0c;流浪宠物信息表&#xff0c;宠物知识信息表&#xff0c;收…

MATLAB | 实用(离谱)小技巧大合集:仅隐藏轴线 | 复杂公式刻度标签 | 渐变背景 | 半透明图例... ...

看到阿昆的科研日常写了一篇如何将轴线隐藏而不隐藏刻度的推送&#xff0c;使用了XRuler中的Axle对象来实现&#xff0c;但我试了一下R2023A版本中不太能直接用&#xff0c;解决了一下&#xff0c;同时讲一下这些有趣的隐藏对象及其其他的用法。 1 隐藏轴框线 假设我们编写了如…

AI绘画新秀-免费使用-Leonardo(Midjourney对手)注册教程

本教程收集于:AIGC从入门到精通教程 AI绘画新秀-免费使用-Leonardo(Midjourney对手) 保姆级注册教程 目录 一、写在前面的话。 二、纯文字教程 2.1 Leonardo注册教程:

手机里的视频删了怎么恢复

手机里的视频删了怎么恢复?手机已经成为一个人生活中不可或缺的工具&#xff0c;尤其是在记录生活中美好瞬间上&#xff0c;很多人都会使用手机去拍下那丰富多彩的视频。然而&#xff0c;有视频就会有清理&#xff0c;不可能所有的视频都合适&#xff0c;但如果不小心将一些重…

网络模块封装

网络模块封装 library-network模块配置依赖一.自定义LiveDataCallAdapterFactory1.定义ApiResponse返回的数据类型2.LiveDataCallAdapter.kt3.LiveDataCallAdapter.kt 二.自定义CustomGsonConverterFactory三.拦截器1.HeaderInterceptor请求头拦截器2.BasicParamsInterceptor参…

SSM框架-SpringMVC

1. SpringMVC 1.1 Spring与Web环境集成 ApplicationContext应用上下文获取方式 应用上下文对象是通过new ClasspathXmlApplicationContext(spring配置文件) 方式获取的&#xff0c;但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(spring配置文件) &…

利用Canal把MySQL数据同步到ES

Canal是阿里巴巴开源的一个数据库变更数据同步工具&#xff0c;主要用于 MySQL 数据库的增量数据到下游的同步&#xff0c;例如同步到 Elasticsearch、HBase、Hive 等。下面是一个基本的步骤来导入 MySQL 数据库到 Elasticsearch。 安装和配置 Canal 首先&#xff0c;需要在你的…

8.防火墙-SNAT和DNAT

文章目录 SNAT-内网客户访问外网服务原理操作实验 DNAT-外网客户访问内网服务原理操作实验 tcpdump SNAT-内网客户访问外网服务 原理 由内网到外网&#xff1a;从内网发到外网的数据包的源IP由私网IP转换成公网IP 由外网到内网&#xff1a;从外网发到内网的数据包的目的IP由公…