开心一刻

  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('张三'),('李四');

   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');

  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>

  配置也很简单, 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

  然后在对应的类或者方法上加上注解 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<Deque<String>> 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<String, DataSource> 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、遇到问题,不要立马一头扎进去,自己实现,多查查,看是否有现成的第三方实现

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

苞米豆的多数据源 → dynamic-datasource-spring-boot-starter,挺香的!的更多相关文章

  1. Druid Spring Boot Starter 从配置到简单运行 -解决zone不匹配 -解决dataSource加载失败

    Druid Spring Boot Starter 中文 | English Druid Spring Boot Starter 用于帮助你在Spring Boot项目中轻松集成Druid数据库连接池 ...

  2. Spring Boot Starter 介绍

    http://www.baeldung.com/spring-boot-starters 作者:baeldung 译者:http://oopsguy.com 1.概述 依赖管理是任何复杂项目的关键部分 ...

  3. spring -boot s-tarter 详解

    Starter POMs是可以包含到应用中的一个方便的依赖关系描述符集合.你可以获取所有Spring及相关技术的一站式服务,而不需要翻阅示例代码,拷贝粘贴大量的依赖描述符.例如,如果你想使用Sprin ...

  4. Spring Boot (一): Spring Boot starter自定义

    前些日子在公司接触了spring boot和spring cloud,有感于其大大简化了spring的配置过程,十分方便使用者快速构建项目,而且拥有丰富的starter供开发者使用.但是由于其自动化配 ...

  5. SpringBoot 之Spring Boot Starter依赖包及作用

    Spring Boot 之Spring Boot Starter依赖包及作用 spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置.日志和YAML. spri ...

  6. Spring boot starter pom的依赖关系说明

    Spring Boot 通过starter依赖为项目的依赖管理提供帮助.starter依赖起始就是特殊的maven依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖. sp ...

  7. Spring Boot Starter列表

    转自:http://blog.sina.com.cn/s/blog_798f713f0102wiy5.html Spring Boot Starter 基本的一共有43种,具体如下: 1)spring ...

  8. 创建自己的Spring Boot Starter

    抽取通用模块作为项目的一个spring boot starter.可参照mybatis的写法. IDEA创建Empty Project并添加如下2个module,一个基本maven模块,另一个引入sp ...

  9. 自己写spring boot starter

    自己写spring boot starter 学习了:<spring boot实战>汪云飞著 6.5.4节 pom.xml <project xmlns="http://m ...

  10. 自定义的Spring Boot starter如何设置自动配置注解

    本文首发于个人网站: 在Spring Boot实战之定制自己的starter一文最后提到,触发Spring Boot的配置过程有两种方法: spring.factories:由Spring Boot触 ...

随机推荐

  1. Nginx常用经典配置|反向代理、HTTPS重定向、端口转发

    二级目录映射 目前前后端项目分离场景多了以后,一般是前端一个端口,后端一个端口. 如前端是https://example.com/index.html,调用的接口是https://example.co ...

  2. Rstudio R get filename full path

    FILENAME=basename(rstudioapi::getActiveDocumentContext()$path) FULLPATH=dirname(rstudioapi::getActiv ...

  3. Java基础学习——Scanner

    import java.util.Scanner; public class HelloWord{ public static void main(String[] args){ Scanner s= ...

  4. 【攻防世界】web练习区write up

    目录: view_source robots backup cookie disabled button weak auth simple php xff referer webshell get p ...

  5. 登录:ORA-12504:TNS:监听程序在CONNECT_DATA中未获得SERVICE_NAME

    问题描述:在用pl/sql登录soctt用户时,显示: 解决办法:在tnsnames.ora文件中添加(文件位置的查找方法见文章末尾) ORCL = (DESCRIPTION = (ADDRESS = ...

  6. Javaweb基础复习------JSON相关知识

    JSON(JavaScript对象表示法) 首先,我们需要知道的是,要使用json语法的话,就需要在Maven项目中导入相关的包,可以参考我之前发过的那个Maven导包那个网址,在里面找到这个页面: ...

  7. 4.0 SDK Workshop 纪实:一起体验多人、多屏幕共享新功能

    在本月初,声网发布了 RTC Native SDK 4.0 版本.该版本提供了更高的开发灵活度,可明显提升实时场景开发效率,并让第三方插件开发更容易.上周六(8月20日),我们组织了一场小型的线下 W ...

  8. 声网 X 在线自习室 同学陪伴、老师监督的在线自习是如何火出圈的?

    实时互联网像触角一样,通过情景的共享延伸开来,链接着我们彼此的线下.线上生活,形成一张不可分割的网络.随着社交直播.在线教育.视频会议成为大众生活不可或缺的一部分的同时,智能手表.智能作业灯.视频双录 ...

  9. springboot切换web服务器

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://mave ...

  10. java数据类型转换有哪几种?看这篇就够了!

    前言 在上一篇文章中,壹哥给大家讲解了Java中的数据类型,从此大家就知道了基本类型和引用类型,尤其是8种基本类型的使用和各自特点.但实际上数据类型的使用还有很多更深入的内容,比如java数据类型直接 ...