天天资讯:苞米豆的多数据源 → dynamic-datasource-spring-boot-starter,挺香的!
发布时间:2023-04-21 12:36:38 来源:博客园
开心一刻

2023年元旦,我妈又开始了对我的念叨

妈:你到底想多少岁结婚


(资料图片仅供参考)

我:60

妈:60,你想找个多大的

我:找个55的啊,她55我60,结婚都有退休金,不用上班不用生孩子,不用买车买房,成天就是玩儿

我:而且一结婚就是白头偕老,多好

我妈直接一大嘴巴子呼我脸上

需求背景

最近接到一个需求,需要从两个数据源获取数据,然后进行汇总展示

一个数据源是MySQL,另一个数据源是SQL Server

楼主是一点都不慌的,因为我写过好几篇关于数据源的文章

spring集成mybatis实现mysql读写分离

原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么

Spring 下,关于动态数据源的事务问题的探讨

我会慌?

但还是有点小拒绝,为什么了?

自己实现的话,要写的东西还是很多,要写AOP,还要实现AbstractRoutingDataSource,还要用到ThreadLocal,...

如果考虑更远一些,事务、数据源之间的嵌套等等,要如何保证正确?

但好在这次需求只是查询,然后汇总,问题就简单很多了,但还是觉得有点小繁琐

当然,如上只是楼主的臆想

有小伙伴可能会问道:能不能合到一个数据源?

楼主只能说:别问了,再问就不礼貌了

既然改变不了,那就盘它

难道就没有现成的多数据源工具?

因为用到了Mybatis-Plus,楼主试着Google了一下

直接一发入魂,眼前一黑,不对,是眼前一亮!

感觉就是它了!

MyBatis-Plus 多数据源

关于苞米豆(baomidou),我们最熟悉的肯定是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 Codedynamic-datasource 使用

基于spring-boot 2.2.10.RELEASE、mybatis-plus 3.1.1搭建

dynamic-datasource-spring-boot-starter也是3.1.1

依赖很简单,pom.xml

    4.0.0    com.lee    mybatis-plus-dynamic-datasource    1.0-SNAPSHOT            org.springframework.boot        spring-boot-starter-parent        2.2.10.RELEASE                1.8        8        8        UTF-8        UTF-8        3.1.1        6.2.1.jre8                            org.springframework.boot            spring-boot-starter-web                            org.projectlombok            lombok                            com.baomidou            mybatis-plus-boot-starter            ${mybatis-plus-boot-starter.version}                            com.baomidou            dynamic-datasource-spring-boot-starter            ${mybatis-plus-boot-starter.version}                                    mysql            mysql-connector-java                                    com.microsoft.sqlserver            mssql-jdbc            ${mssql-jdbc.version}            
View Code

配置也很简单,application.yml

server:  port: 8081spring:  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: falsemybatis-plus:  mapper-locations: classpath:mappers/*.xml  configuration:    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
View Code

然后在对应的类或者方法上加上注解DS("数据源名称")即可,例如

我们来看下效果

是不是很神奇?

完整代码:mybatis-plus-dynamic-datasource

原理探究

@DS用于指定数据源,可以注解在方法上或类上,同时存在则采用就近原则 方法上注解 优先于 类上注解

这可不是我瞎说,官方文档就是这么写的

难道一个@DS就有如此强大的功能?你们不信,我也不信,它背后肯定有人!

那么我们就来揪一揪背后的它

怎么揪了,这又是个难题,我们先打个断点,看一下调用栈

点一下,瞬间高潮了,不是,是瞬间清醒了

红线框住的,分 2 点:1:determineDatasource,2:DynamicDataSourceContextHolder.push

我们先看determineDatasource

1、获取Method对象

2、该方法上是否有 DS 注解,有则取方法的 DS 注解,没有则取方法对应的类上的 DS 注解;这个看明白了没?

3、获取注解的值,也就是@DS("mysql_db")中的mysql_db

4、如果数据源名不为空并且数据原名以动态前缀(#)开头,则你们自己去跟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> LOOKUP_KEY_HOLDER,回忆上来了没?

出栈,获取到当前的数据源名;接下来该分析谁了?

那肯定是getDataSource方法

1、如果数据源为空,那么直接返回默认数据源,对应配置文件中的

2、分组数据源,我们的示例代码那么简单,应该没涉及到这个,先不管

3、所有数据源,是一个LinkHashMap,key 是数据源名,value 是数据源

可想而知,我们示例的数据源获取就是从该 map 获取的

4、是否启用严格模式,默认不启动。严格模式下未匹配到数据源直接报错,,非严格模式下则使用默认数据源primary所设置的数据源

5、对应 4,未开启严格模式,未匹配到数据源则使用primary所设置的数据源

那现在又该分析谁?肯定是dataSourceMap的值是怎么put进去的

我们看哪些地方用到了dataSourceMap

发现就一个地方进行了put

那这个addDataSource方法又在哪被调用了?

DynamicRoutingDataSource实现了InitializingBean,所以在启动过程中,它的afterPropertiesSet方法会被调用,至于为什么,大家自行去查阅

接下来该分析什么?那肯定是Map dataSources = provider.loadDataSources();

我们跟进loadDataSources(),发现有两个类都有该方法

那么我们应该跟谁?有两种方法

1、凭感觉,我们的配置文件是yml

2、打断点,重新启动项目,一目了然

YmlDynamicDataSourceProvider的loadDataSources方法如下

(这里留个疑问:dataSourcePropertiesMap存放的是什么,值是如何 put 进去的?

继续往下跟createDataSourceMap方法

1、配置文件中的数据源属性,断点下就很清楚了

2、根据数据源属性创建数据源,然后放进dataSourceMap中

创建数据源的过程就不跟了,感兴趣的自行去研究

至此,不知道大家清楚了没? 我反正是晕了

总结

1、万变不离其宗,多数据源的原理是不变的

原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么

2、苞米豆的多数据源的自动配置类

com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

这个配置类很重要,很多重要的对象都是在这里注入到Spring容器中的

关于自动配置,大家可参考:springboot2.0.3源码篇 - 自动配置的实现,发现也不是那么复杂

3、遇到问题,不要立马一头扎进去,自己实现,多查查,看是否有现成的第三方实现

自己实现,很容易踩别人踩过的坑,容易浪费时间;另外局限性太大,不易拓展,毕竟一人之力有限

标签:

上一篇:

下一篇: