浅析动态切换数据源的原理(接上篇)
提前祝大家双节快乐
上一篇我们实现了多数据源动态切换的功能,这次我们来看一下是如何实现的。
没看过上一篇的点击这里
中秋快乐
我们在DynamicDataSourceConfig
中配置了所有数据源信息,并存在一个map中
因为我们的DynamicDataSource
继承了AbstractRoutingDataSource
抽象类,AbstractRoutingDataSource
中有几个重要的属性在这里我们用到了
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;
-
targetDataSources
:持有我们多数据源的所有数据源,key为不重复的对象用于唯一标识一个数据源,value为数据源DataSource实例 -
defaultTargetDataSource
:默认的数据源实例
可以看到我们在配置类中new DynamicDataSource
在构造方法中传了两个参数,就对应了defaultTargetDataSource
和targetDataSources
,这样DynamicDataSource就持有了我们的默认数据源和所有数据源。
然后我们可以看到DynamicDataSource
实现了AbstractRoutingDataSource
抽象类的抽象方法determineCurrentLookupKey()
:
这个方法的作用就是返回一个key,该key对应当前你希望使用的DataSource。在这个里,我们优化了一下,使用一个类DataSourceHolder
持有我们的这个key,所以DynamicDataSource
方法就变得很简洁了,就是在determineCurrentLookupKey
方法中调用DataSourceHolder
的静态方法getDataSourceName
获取到当前用户想要使用的DataSource
的key
。
在AbstractRoutingDataSource
的determineTargetDataSource
方法中调用了determineCurrentLookupKey
方法:
我们可以看到拿到key后,是从resolvedDataSources
(一个map对象)中get对应的DataSource
,get不到返回resolvedDefaultDataSource
,看样子是默认数据源
@Nullable
private Map<Object, DataSource> resolvedDataSources;
但是resolvedDataSources
和resolvedDefaultDataSource
都不是刚刚我们所介绍的targetDataSources
和defaultTargetDataSource
。
那是因为做了赋值操作,
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#afterPropertiesSet
AbstractRoutingDataSource
实现了InitializingBean
接口的afterPropertiesSet
,会在实例化bean后调用此方法(AbstractAutowireCapableBeanFactory#invokeInitMethods
方法调用的),所以resolvedDataSources
和resolvedDefaultDataSource
就有值了。
继续回到determineTargetDataSource
方法,该返回DataSource
实例,既然是返回实例,肯定是有地方去获取DataSource
,因为我们用的持久层框架是mybatis
,所以获取DataSource
的这个操作势必是mybatis
发起的。
清楚了这一点就好办了,那就要看mybatis
了,查阅资料我们可以发现,mybatis包中有这样一个类负责管理JDBC连接的生命周期,它就是org.mybatis.spring.transaction.SpringManagedTransaction
(mybatis-spring.jar
,spring
集成mybatis
的jar包)它实现了mybatis
中的Transaction
接口
我们可以看到接口中有一个getConnection()
方法,即是要从数据库连接池中获取一个连接:
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
在连接为空时,调用openConnection()
方法获取连接,我们看看openConnection
方法做了什么操作:
该方法第一个调用DataSourceUtils.getConnection(this.dataSource)
获取连接,其调用DataSourceUtils
中的doGetConnection(dataSource)
获取连接
然后在这个方法内部又调用fetchConnection(dataSource)
方法获取连接
fetchConnection(datasource)
内直接就调用了DataSource
对象的getConnection
方法
因为我们的DynamicDataSource
没有重写getConnection
方法,那么就会调父类的
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
可以看到先调了determineTargetDataSource
方法获取DataSource
再getConnection()
。determineTargetDataSource
方法刚刚我们已经看过了。
总结
大致思想就是,mybatis执行SQL时会从DataSource拿一个JDBC连接,所以spring巧妙的利用了这个特点,它提前拿到多个数据源的实例信息,在mybatis获取连接时根据用户的指令,动态的选择返回哪个连接,这些操作对mybatis是透明的。
spring把一些通用方法高度抽象到AbstractRoutingDataSource
抽象类中,使用两个变量管理数据源,targetDataSources
和defaultTargetDataSource
,然后再预留出determineCurrentLookupKey
方法供我们实现,即只需要返回一个key就可以,返回的key用于从targetDataSources
中选取出我们指定的数据源。
所以,除了determineCurrentLookupKey
方法外,其他的操作对于使用者来说也是透明的,使用者只需要关心如何在determineCurrentLookupKey
方法中实现自己的选择数据源的规则即可。
只不过,我们是通过AOP切面拦截的方式增强方法,在持久层方法执行前即在mybatis执行SQL前我们事先把DataSource
换成我们指定的即可。
在这里我们换数据源的方式是通过一个DataSourceHolder
类中的ThreadLocal
实现的,原因是为了保证多线程并发环境下不同线程切换数据源时不会乱,Threadlocal
线程独有的一个对象,在其内部保存我们的key,在determineCurrentLookupKey
中获取并返回即可。
代码已上传到码云,springboot版本和SSM都有
感兴趣的可以去下载
https://gitee.com/itwalking/springboot-dynamic-datasource
https://gitee.com/itwalking/ssm-dynamic-datasource
不要吝啬你的赞?,求三连,点赞、收藏、转发
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- MATP ManyTask Multitask Problem 和 Solution 的变量范围
- Sinopia安装部署
- Nginx的405 not allowed错误解决
- Linux免密登陆
- 一起来学matlab-matlab学习笔记8 基本绘图命令_2基本绘图操作
- 用一个图书库实例搞懂二分搜索树的底层原理
- 自已做动画及编写程序搞清楚最大堆的实现原理
- 一起来学演化计算-matlab基本函数strcmp num2str 字符串格式
- 一起来学matlab-matlab学习笔记8 基本绘图命令_1 图形窗口简介
- 根据barcode过滤bam文件
- biopython - 比较两个序列的相似性
- 使用阿里函数计算同步OSS增量对象到COS
- GitLab定时备份及恢复
- MySQL 的B+树索引.
- Spring全家桶的深入学习(一):Spring起步