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

news2025/1/20 3:55:03

目录

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.coloradmin.cn/o/532281.html

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

相关文章

Win11的两个实用技巧系列之关闭分屏模式方法

怎么关闭Win11电脑分屏模式?Win11关闭分屏模式方法 有用户在使用电脑的时候不小心开启了电脑的分屏模式&#xff0c;导致自己无法正常的进行电脑操作了&#xff0c;本文就为大家带来了Win11关闭分屏模式方法&#xff0c;一起看看吧 Win11电脑分屏模式怎么关闭&#xff1f;有用…

oracle杂记

rlwrap…rpm 可以在sqlplus上翻下翻 然后alias sqlplus‘rlwrap sqlplus’ alias rmanrlwrap rman 数据库是由存储部分*.dbf 和运行的process和所使用的内存区域构成 远程安装前确认是否图形化能打开 yum -y install xorg-x11-apps xclock 启动顺序&#xff1a; nomount 启…

Unity VR开发教程 OpenXR+XR Interaction Toolkit 番外(一)用 Grip 键, Trigger 键和摇杆控制手部动画

文章目录 &#x1f4d5;制作手部动画&#x1f4d5;设置 Animation Controller&#x1f4d5;添加触摸摇杆的 Input Action&#x1f4d5;代码部分 在大部分 VR 游戏中&#xff0c;手部的动画通常是由手柄的三个按键来控制的。比如 Grip 键控制中指、无名指、小拇指的弯曲&#xf…

城际铁路列车控制系统

城际铁路列控系统在CTCS-2级列控系统基础上&#xff0c;应具备站间自动运行、车站定点停车及车站通过、折返作业、列车运行自动调整、车门/站台门&#xff08;安全门或屏蔽门&#xff09;防护及联动控制、列车运行节能控制等自动运行相关功能。 1、车载设备工作模式 城际铁路…

Access Specifier Manipulation解决方案

解释&#xff1a;AccessibleObject API 允许程序员绕过由 Java 访问说明符提供的 access control 检查。特别是它让程序员能够允许反映对象绕过 Java access control&#xff0c;并反过来更改私有字段或调用私有方法、行为&#xff0c;这些通常情况下都是不允许的 在面向对象编…

【微信小程序】微信支付接入全流程

一. 前置条件 接入支付首先得需要有企业资质&#xff0c;并开通企业对公户注册微信支付并进行对公户打款认证 二. 开始接入 1. 下载微信支付的AP证书 2. 服务端接入微信支付 2.1 引入相关maven配置 <dependency><groupId>com.github.binarywang</groupId&…

Nginx内存池(二)——小块内存分配

内存分配源代码&#xff1a; void * ngx_palloc(ngx_pool_t *pool, size_t size) { #if !(NGX_DEBUG_PALLOC)if (size < pool->max) { // 用内存池分配内存return ngx_palloc_small(pool, size, 1);} #endif// mallocreturn ngx_palloc_large(pool, size); }static ngx_…

对话风变科技 CTO|从线上教育服务商到教育资源供给平台,风变背后的第二增长曲线思维

Authing 是用户中心团队&#xff0c;我们是业务系统&#xff0c;大家冲刺一个目标、再做合并&#xff0c;然后让基于多租户的 B 端产品成功上线。那个阶段刚好有个客户卡在当时的时间点&#xff0c;一定要赶着上线&#xff0c;最后 Authing 的协作让我们赢得了客户信任&#xf…

基于scrapy+mongodb的校园数据获取

Scrapy以及MongoDB介绍&#xff1a; Scrapy: Scrapy是一个Python编写的开源网络爬虫框架&#xff0c;可以帮助开发人员高效、快速地从互联网上抓取、提取和处理数据。Scrapy拥有强大的可扩展性&#xff0c;可以通过定制管道、中间件和Spider来自定义爬虫行为&#xff0c;同时它…

( 位运算 ) 371. 两整数之和 ——【Leetcode每日一题】

❓371. 两整数之和 难度&#xff1a;中等 给你两个整数 a 和 b &#xff0c;不使用 运算符 和 - ​​​​​​​&#xff0c;计算并返回两整数之和。 示例 1&#xff1a; 输入&#xff1a;a 1, b 2 输出&#xff1a;3 示例 2&#xff1a; 输入&#xff1a;a 2, b 3 输出…

源码解读guava cache get接口的秘密

guava cache是谷歌开源的一种本地缓存&#xff0c;实现原理类似于ConcurrentHashMap&#xff0c;使用segments分段锁&#xff0c;保证线程安全&#xff0c;支持高并发场景。同时支持多种类型的缓存清理策略&#xff0c;包括基于容量的清理、基于时间的清理、基于引用的清理等。…

WPF基础使用

wpfwindows presentation foundation 官方文档 https://learn.microsoft.com/zh-cn/dotnet/api/?viewnet-7.0 一&#xff1a;WPF窗口设置为透明 在wpf中要将窗口设置为透明&#xff0c;除了将窗口背景色的Alpha分量设置为0以外&#xff0c;你还必须将窗口的AllowsTransparency…

STM32串口编程基础知识讲解

文章目录 前言一、串行通信和并行通信二、全双工&#xff0c;半双工&#xff0c;单工传输三、同步通信和异步通信四、波特率总结 前言 本篇文章给大家介绍一下串口的基础编程知识&#xff0c;这些知识是属于串口的理论知识&#xff0c;在开始学习编写代码的时候必须要掌握这些…

消息队列中间件 - RabbitMQ消息的持久化、确认机制、死信队列

持久化和应答机制Ack 消息队列中间件系列的最后一篇了&#xff0c;RabbitMQ消息的持久化、确认机制、死信队列、负载均衡等一系列进行说明。 消息持久化 当RabbitMq重启以后&#xff0c;未消费的消息&#xff0c;可以在服务重启后继续消费&#xff0c;不会丢失。 应答机制A…

nodejs处理xlsx文件生成json文件

nodejs处理xlsx文件有好几种方式&#xff0c;这里用的是js-xlsx库&#xff1b; 需求 有一个 xlsx 的文件&#xff0c;里面有几个不同的 sheet&#xff0c;需要读取这个表格中不同 sheet 的数据&#xff0c;并且为每个 sheet 生成对应的 json 文件。 例如有一个名为 template…

草根测试员的涨薪之路:我是如何从0到月薪20K的?(仅供借鉴)

我是一名转IT测试人&#xff0c;我的专业是化学&#xff0c;去化工厂实习才发现这专业的坑人之处&#xff0c;化学试剂害人不浅&#xff0c;有毒&#xff0c;易燃易爆&#xff0c;实验室经常用丙酮&#xff0c;甲醇&#xff0c;四氯化碳&#xff0c;接触多了&#xff0c;吃个饭…

网络安全:Hydra 端口爆破工具.(九头蛇)

网络安全&#xff1a;Hydra 端口爆破工具.&#xff08;九头蛇&#xff09; Hydra 也叫九头蛇&#xff0c;是一款开源的暴力PJ工具&#xff0c;集成在kali当中。可以对多种服务的账号和密码进行爆破&#xff0c;包括 Web 登录、数据库、SSH、FTP 等服务. 目录&#xff1a; 网络…

内网渗透—域防火墙策略同步、不出网隧道上线

内网渗透—域防火墙策略同步、不出网隧道上线 1. 前言2. 域防火墙2.1. 域控开启防火墙同步测试2.1.1. 查看域主机防火墙策略2.1.2. 域控防火墙策略下发同步2.1.2.1. 创建组策略2.1.2.2. 编辑组策略2.1.2.3. 编辑防火墙2.1.2.4. 同步防火墙策略 2.2. 域控出入站规则同步2.2.1. 查…

【数据结构】队列及其实现

目录 &#x1f60e;前言 认识队列 队列的初始化 队列判空 数据队尾入队 数据队头出队 取队头数据 取队尾数据 队列数据的个数 队列销毁 总结 &#x1f60e;前言 上次我们学习了栈及其实现&#xff0c;当然也少不它的好兄弟队列啦&#xff0c;今天我们开始队列的学习队…

大模型Founation Model

一、背景 自从chatgpt&#xff0c;gpt4以特别好的效果冲入人们的视野中&#xff0c;也使得AI产业发生了巨大变革&#xff0c;从17年以来的bert&#xff0c;将AI的各种领域都引入bert类的fine-tune方法&#xff0c;来解决单个领域单个任务的一一个预训练模型。在学术界和工业界…