开心一刻
2023年元旦,我妈又开始了对我的念叨
妈:你到底想多少岁结婚
我:60
妈:60,你想找个多大的
我:找个55的啊,她55我60,结婚都有退休金,不用上班不用生孩子,不用买车买房,成天就是玩儿
我:而且一结婚就是白头偕老,多好
我妈直接一大嘴巴子呼我脸上
需求背景
最近接到一个需求,需要从两个数据源获取数据,然后进行汇总展示
MySQL ,另一个数据源是 SQL Server
楼主是一点都不慌的,因为我写过好几篇关于数据源的文章
spring集成mybatis实现mysql读写分离
原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么
Spring 下,关于动态数据源的事务问题的探讨
我会慌?
但还是有点小拒绝,为什么了?
AOP ,还要实现 AbstractRoutingDataSource ,还要用到 ThreadLocal
如果考虑更远一些,事务、数据源之间的嵌套等等,要如何保证正确?
但好在这次需求只是查询,然后汇总,问题就简单很多了,但还是觉得有点小繁琐
当然,如上只是楼主的臆想
有小伙伴可能会问道:能不能合到一个数据源?
楼主只能说:别问了,再问就不礼貌了
既然改变不了,那就盘它
难道就没有现成的多数据源工具?
Mybatis-Plus ,楼主试着 Google
直接一发入魂,眼前一黑,不对,是眼前一亮!
感觉就是它了!
MyBatis-Plus 多数据源
MyBatis-Plus
但旗下还有很多其他优秀的组件
多数据源就是其中一个,今天我们就来会会它
数据源准备
docker 准备一个 MySQL 和 SQL Server ,图省事,两个数据库服务器放到同个 docker
有小伙伴会觉得放一起不合适,有单点问题!
楼主只是为了演示,纠结那么细,当心敲你狗头
MySQL 版本: 8.0.27
datasource_mysql ,建表: tbl_user
CREATE DATABASE datasource_mysql;
USE datasource_mysql;
CREATE TABLE tbl_user (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
user_name VARCHAR(50),
PRIMARY KEY(id)
);
INSERT INTO tbl_user(user_name) VALUES('张三'),('李四');
View Code
SQL Server 版本: Microsoft SQL Server 2017 ...
datasource_mssql ,建表: tbl_order
CREATE DATABASE datasource_mssql;
USE datasource_mssql;
CREATE TABLE tbl_order(
id BIGINT PRIMARY KEY IDENTITY(1,1),
order_no NVARCHAR(50),
created_at DATETIME NOT NULL DEFAULT(GETDATE()),
updated_at DATETIME NOT NULL DEFAULT(GETDATE())
);
INSERT INTO tbl_order(order_no) VALUES('123456'),('654321');
View Code
dynamic-datasource 使用
spring-boot 2.2.10.RELEASE 、 mybatis-plus 3.1.1
dynamic-datasource-spring-boot-starter 也是 3.1.1
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.lee</groupId>
<artifactId>mybatis-plus-dynamic-datasource</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.10.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mybatis-plus-boot-starter.version>3.1.1</mybatis-plus-boot-starter.version>
<mssql-jdbc.version>6.2.1.jre8</mssql-jdbc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!-- MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- SQL Server 驱动-->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>${mssql-jdbc.version}</version>
</dependency>
</dependencies>
</project>
View Code
application.yml
server:
port: 8081
spring:
application:
name: dynamic-datasource
datasource:
dynamic:
datasource:
mssql_db:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://10.5.108.225:1433;DatabaseName=datasource_mssql;IntegratedSecurity=false;ApplicationIntent=ReadOnly;MultiSubnetFailover=True
username: sa
password: Root#123456
mysql_db:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.5.108.225:3306/datasource_mysql?useSSL=false&useUnicode=true&characterEncoding=utf-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
primary: mssql_db
strict: false
mybatis-plus:
mapper-locations: classpath:mappers/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
View Code
DS("数据源名称")
我们来看下效果
是不是很神奇?
完整代码:mybatis-plus-dynamic-datasource
原理探究
@DS
这可不是我瞎说,官方文档就是这么写的
@DS
那么我们就来揪一揪背后的它
怎么揪了,这又是个难题,我们先打个断点,看一下调用栈
点一下,瞬间高潮了,不是,是瞬间清醒了
determineDatasource ,2: DynamicDataSourceContextHolder.push
determineDatasource
Method
2、该方法上是否有 DS 注解,有则取方法的 DS 注解,没有则取方法对应的类上的 DS 注解;这个看明白了没?
@DS("mysql_db") 中的 mysql_db
并且数据原名以动态前缀(#)开头,则你们自己去跟 dsProcessor.determineDatasource
否则则直接返回数据源名
针对案例的话,这里肯定是返回类上的数据源名(方法上没有指定数据源,也没有以动态前缀开头)
DynamicDataSourceContextHolder.push
LOOKUP_KEY_HOLDER
是一个栈,而非楼主在spring集成mybatis实现mysql读写分离 采用的
至于为什么,人家注释已经写的很清楚了,试问楼主的实现能满足一级一级数据源切换的调用场景吗?
LOOKUP_KEY_HOLDER 的类型还是 ThreadLocal
接下来该分析什么?
我们回顾下:原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么
直接跳到总结
框住的 3 条,上面的 2 条在上面已经分析过了把,是不是?你回答是就完事了
DynamicDataSource 是自实现的类,继承了 spring-jdbc 的 AbstractRoutingDataSource
AbstractRoutingDataSource
spring-jdbc 下,而不是在 com.baomidou
AbstractRoutingDataSource ? 我们来看看 AbstractDataSource
看到了没,那么我们接下来就分析它
determineDataSource
DynamicRoutingDataSource 的 determineDataSource
DynamicDataSourceContextHolder
ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER
出栈,获取到当前的数据源名;接下来该分析谁了?
getDataSource
1、如果数据源为空,那么直接返回默认数据源,对应配置文件中的
2、分组数据源,我们的示例代码那么简单,应该没涉及到这个,先不管
LinkHashMap ,key 是 数据源名
可想而知,我们示例的数据源获取就是从该 map 获取的
primary
primary
dataSourceMap 的值是怎么 put
dataSourceMap
put
addDataSource
DynamicRoutingDataSource 实现了 InitializingBean ,所以在启动过程中,它的 afterPropertiesSet
Map<String, DataSource> dataSources = provider.loadDataSources();
loadDataSources()
那么我们应该跟谁?有两种方法
yml
2、打断点,重新启动项目,一目了然
YmlDynamicDataSourceProvider 的 loadDataSources
dataSourcePropertiesMap 存放的是什么,值是如何 put 进去的?)
createDataSourceMap
1、配置文件中的数据源属性,断点下就很清楚了
dataSourceMap
创建数据源的过程就不跟了,感兴趣的自行去研究
至此,不知道大家清楚了没? 我反正是晕了
总结
1、万变不离其宗,多数据源的原理是不变的
原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么
2、苞米豆的多数据源的自动配置类
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration
Spring
关于自动配置,大家可参考:springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂
3、遇到问题,不要立马一头扎进去,自己实现,多查查,看是否有现成的第三方实现
自己实现,很容易踩别人踩过的坑,容易浪费时间;另外局限性太大,不易拓展,毕竟一人之力有限