禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java基础

  • 并发编程

  • JVM相关

  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 深入解读 Spring MVC:Web 开发的得力助手
    • 探索 Spring 事务的奥秘
    • 来聊聊事务监听
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 深入剖析源码速通Spring多数据源问题
      • 写在文章开头
      • spring数据源加载原理详解
        • 数据源的加载
        • 数据源的切换
      • 需求和设计思路
      • 实现
      • 逻辑实现
      • 优化
      • 小结
      • 参考文献
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于sharding-jdbc拓展点实现复杂分库分表算法
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
    • 一文快速掌握高性能内存队列Disruptor
    • 安利一个轻量级流程引擎compileflow
    • 来聊聊一个轻量级的有限状态机Cola-StateMachine
  • Java核心技术
  • 深入理解Spring框架
sharkchili
2023-05-09
目录

深入剖析源码速通Spring多数据源问题

# 写在文章开头

之前写过一篇关于多数据源的实践,虽然写的很详细,但是个人认为没有把多数据源的原理和设计讲透彻,所以再次拾起这篇文章进行修饰加工,希望对你有帮助。

当一个接口请求涉及多库操作时,就涉及多数据源问题,举个例子:当我们进行下单时,涉及用户表、商品表、订单表,而这些数据表分别在基础信息库、商品管理库、详单管理库。对此我们就需要SpringBoot完成多数据源加载和动态切换。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。

因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# spring数据源加载原理详解

# 数据源的加载

为了解决上述问题,笔者特地查看了Spring数据源加载的源码,在调试中看到一个AbstractRoutingDataSource是决定数据源的关键。查看其类图可以发现它用到了InitializingBean这个接口,说明在bean加载完成之后会针对数据源进一步的处理操作。

查看AbstractRoutingDataSource对InitializingBean的实现可以看到它的逻辑,非常简单,从targetDataSources数据源信息的键值对并将其存入resolvedDataSources中。很明显从这个扩展点可以推断出Spring允许加载多个数据源。

@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
			
		//将targetDataSources所有的值存到resolvedDataSources中
		this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
	//如果resolvedDefaultDataSource 不为空,则将当前项目的defaultTargetDataSource 设置为defaultTargetDataSource
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

转换成图解就像下面这样,可以看到上述的代码会将yml配置中的数据源信息存入resolvedDataSources这个map中。

# 数据源的切换

了解了数据源的加载,接下来我们就需要了解数据源的切换了,对此笔者专门写下一段数据库查询的业务代码并插入断点了解其内部的执行过程:

userMapper.selectByPrimaryKey(id)
1

通过断点调试Mybatis代理生成的mapper的底层逻辑,笔者看到一个名为DataSourceUtils的工具类,它会通过外部传入的数据源调用doGetConnection返回一个SQL连接:

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
		try {
			return doGetConnection(dataSource);
		}
		//.....
		}
	}
1
2
3
4
5
6
7

查看doGetConnection方法,笔者得到最关键的一段代码fetchConnection,该方法就会获取SQL方法核心方法:

Connection con = fetchConnection(dataSource);
1

最终它又会走到我们上文提到加载数据源的抽象类AbstractRoutingDataSource,调用其getConnection方法,其内部会调用determineTargetDataSource获取数据源实例,再从这个数据源中拿到一个connection实例,从而完成一次SQL执行。

@Override
	public Connection getConnection() throws SQLException {
	//调用下方的determineTargetDataSource获取相应数据源
		return determineTargetDataSource().getConnection();
	}


protected DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		//determineCurrentLookupKey获取数据源的key值
		Object lookupKey = determineCurrentLookupKey();
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		//略
		return dataSource;
	}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

查看determineCurrentLookupKey这个方法发现它是一个抽象类,也就是说这是框架允许我们自行实现的方法。

@Nullable
	protected abstract Object determineCurrentLookupKey();

1
2
3

结合上下文我们由此可知,我们可以通过继承AbstractRoutingDataSource重写determineCurrentLookupKey告知spring当前操作要用哪个key的数据源,然后让其基于这个数据源获取对应的connection从而完成SQL操作:

# 需求和设计思路

通过源码了解了Spring数据源的工作流程之后,我们不妨提出这样一个接口,该接口用于购买汽车,外部会传入汽车id、用户id,基于这两个信息完成如下操作:

  1. 从db1查询用户信息
  2. 从db2查询汽车信息
  3. 到db3完成下单

从源码中了解了spring的设计思路之后,我们现在就不妨设计一下多数据源切换的实现思路。首先是技术实现上:

  1. maven引入相关依赖。
  2. 编写多数据源的配置。
  3. 编写配置类将数据源加载到spring容器中。
  4. 编写一个线程数据源管理类,分别存放每一个请求线程的数据源key值。
  5. 编写一个数据源管理类,负责加载项目运行时的数据源加载和存放。
  6. 继承AbstractRoutingDataSource重写determineCurrentLookupKey基于线程数据源管理类实现获取最新数据源的逻辑。

业务实现上:

  1. 编写用户信息查询功能。
  2. 编写汽车信息查询功能。
  3. 编写下单信息存储功能。

好了,话不多说,现在就开始实现这个需求。

# 实现

引入相关依赖这步骤笔者就不多赘述了,读者只要按需引入对应web和MySQL相关依赖即可。

然后就是编写数据源配置类将数据源加载到容器的逻辑,我们首先需要将多个数据源的信息添加到yml文件中。

spring:
  datasource:
    druid:
      type: com.alibaba.druid.pool.DruidDataSource
      master:
        url: jdbc:mysql://rm-xxxxxxx.mysql.rds.aliyuncs.com:3306/db1?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: xxxxxx
        password: xxxxx
        driver-class-name: com.mysql.jdbc.Driver
      slave:
        url: jdbc:mysql://rm-xxxxxxxxxx.mysql.rds.aliyuncs.com:3306/db2?useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: xxxxxxx
        password: xxxxxxxx
        driver-class-name: com.mysql.jdbc.Driver
1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后我们编写一个DruidConfig配置类,将上面master和slave库和数据源的bean绑定并存到spring容器中。

/**
 * 数据源配置类
 */
@Configuration
@MapperScan("com.example.springdatasource.mapper")
public class DruidConfig {

    /**
     * 主库数据源bean,和spring.datasource.druid.master配置绑定
     * @return
     */
    @Bean(name = CommonConstant.MASTER)
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource()
    {
        DruidDataSource master = DruidDataSourceBuilder.create().build();
        return master;
    }

    /**
     * 从库数据源bean,和spring.datasource.druid.slave绑定
     * @return
     */
    @Bean(name = CommonConstant.SLAVE)
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource()
    {
        DruidDataSource slave = DruidDataSourceBuilder.create().build();
        return slave;
    }

    /**
     * 动态数据源bean
     * @return
     */
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource()
    {
        //创建一个存放数据源的map
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        //将上述两个数据源存放到map中
        dataSourceMap.put(CommonConstant.MASTER,masterDataSource());
        dataSourceMap.put(CommonConstant.SLAVE,slaveDataSource());


        //设置动态数据源,默认为master配置的数据源,并指定数据源的map
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);


        //将数据源信息备份在defineTargetDataSources中
        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);

        return dynamicDataSource;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

我们都知道在spring boot这个web应用中,每一个请求对应一个线程,所以我们完全可以通过将每一个请求当前时刻所要用到的数据源的key存在threadLocal中,确保每一个请求的key互相隔离:

所以我们编写了一个DynamicDataSourceHolder 通过ThreadLocal实现线程间的数据源隔离。

/**
 * 数据源切换处理类
 *
 */
@Slf4j
public class DynamicDataSourceHolder {
    /**
     * 为每个线程存放当前数据源的ThreadLocal
     */
    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();

    /**
     * 为当前线程切换数据源
     */
    public static void setDynamicDataSourceKey(String key) {
        log.info("数据源切换key:{}", key);
        DYNAMIC_DATASOURCE_KEY.set(key);
    }

    /**
     * 获取动态数据源的名称,默认情况下使用mater数据源
     */
    public static String getDynamicDataSourceKey() {
        String key = DYNAMIC_DATASOURCE_KEY.get();
        if (ObjectUtils.isEmpty(key)) {
            key = CommonConstant.MASTER;
        }
        log.info("获取数据源,key:{}", key);
        return key;
    }

    /**
     * 将ThreadLocal置空,移除当前数据源
     */
    public static void removeDynamicDataSourceKey() {
        log.info("移除数据源:{}", DYNAMIC_DATASOURCE_KEY.get());
        DYNAMIC_DATASOURCE_KEY.remove();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

我们后续可能用到不止两个的数据库,所以我们可能会将数据源的信息保存到数据源中,考虑到这一点,我们编写了一个数据源管理类,负责将用户从数据库中查出来的数据源信息存到容器中。

为了做到这一点,我们首先需要编写一个数据源的类,记录一下数据库查出来的数据源信息。

/**
 * 数据源对象类
 */
public class DataSourceInfo {

    private String userName;
    private String passWord;
    private String url;
    private String dataSourceKey;

  //get set...
}
1
2
3
4
5
6
7
8
9
10
11
12

然后我们就来编写数据源管理类,实现数据源加载和保存的逻辑,代码含义笔者都已详尽注释,读者可以自行查阅。

/**
 * 数据源管理工具类
 */
@Slf4j
@Component
public class DataSourceUtil {

    @Resource
    DynamicDataSource dynamicDataSource;

    /**
     * 测试数据源是否可用,如果可用即直接返回
     * @param dataSourceInfo
     * @return
     * @throws SQLException
     */
    public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) throws SQLException {
        //创建数据源对象
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(dataSourceInfo.getUrl());
        druidDataSource.setUsername(dataSourceInfo.getUserName());
        druidDataSource.setPassword(dataSourceInfo.getPassWord());
        druidDataSource.setBreakAfterAcquireFailure(true);
        druidDataSource.setConnectionErrorRetryAttempts(0);
        try {
            //尝试连接数据源
            druidDataSource.getConnection(2000);
            log.info("数据源:{}连接成功", JSONUtils.toJSONString(dataSourceInfo));
            return druidDataSource;
        } catch (SQLException e) {
            log.error("数据源 {} 连接失败,用户名:{},密码 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassWord());
            return null;
        }
    }

    /**
     * 将外部数据源存到dynamicDataSource并调用afterPropertiesSet刷新
     * @param druidDataSource
     * @param dataSourceName
     */
    public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){
        Map<Object, Object> defineTargetDataSources = dynamicDataSource.getDefineTargetDataSources();
        //存到defineTargetDataSources这个map中
        defineTargetDataSources.put(dataSourceName, druidDataSource);
        dynamicDataSource.setTargetDataSources(defineTargetDataSources);
        //调用afterPropertiesSet重新遍历map中的数据源键值对存到resolvedDataSources中
        dynamicDataSource.afterPropertiesSet();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

到上述步骤为止,我们已经编写动态数据源应用的行为,那么我们又该如何获取最新的数据源呢?还记得我们上文编写的DynamicDataSourceHolder吗?它通过ThreadLocal将可以得到当前线程的数据源的key。 基于这点我们直接继承AbstractRoutingDataSource重写determineCurrentLookupKey方法,让这个方法从DynamicDataSourceHolder获取当前sql操作要用到哪个datasource的key。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DynamicDataSource extends AbstractRoutingDataSource {
    //备份所有数据源信息,
    private Map<Object, Object> defineTargetDataSources;

    /**
     * 返回当前线程需要用到的数据源bean
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDynamicDataSourceKey();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 逻辑实现

自此我们所有的工作都准备好了,接下来就开始写业务代码了,我们要写一个用户下单购买汽车的流程:

  1. 查询用户。
  2. 查询汽车。
  3. 如果都存在则创建订单,并保存到数据库。
  4. 返回成功标识。

代码如下,虽然调试一下功能确实没问题。但不难看到笔者都是手动实现数据源切换,将业务和非业务代码耦合在一起,非常不方便。

 @Resource
    private DataSourceUtil dataSourceUtil;
    @Resource
    private CommonMapper commonMapper;


    @PostMapping("/orderCar")
    public boolean dynamicDataSourceTest(@RequestBody Map<String,Object> params) throws SQLException {
        Map<String, Object> map = new HashMap<>();
        //在主库中查询汽车信息列表
        User user = commonMapper.getUserInfo((String) params.get("uid"));
        if (user==null){
            throw new RuntimeException("用户不存在");
        }


        //在从库中查询db3数据源信息
        DynamicDataSourceHolder.setDynamicDataSourceKey(CommonConstant.SLAVE);
        DataSourceInfo dataSourceInfo = commonMapper.getNewDataSourceInfo("slave2");
        map.put("dataSource", dataSourceInfo);
        log.info("数据源信息:{}", dataSourceInfo);
        //尝试db3的连接是否可用
        DruidDataSource druidDataSource = dataSourceUtil.createDataSourceConnection(dataSourceInfo);


        Car car=null;

        if (Objects.nonNull(druidDataSource)) {
            //如果db3可用则直接将db3存到动态数据源map中
            dataSourceUtil.addDefineDynamicDataSource(druidDataSource, dataSourceInfo.getDataSourceKey());
            //切换当前数据源为db3
            DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDataSourceKey());
            //在新的数据源中查询用户信息
             car = commonMapper.getCarInfo((String) params.get("cid"));
            if (car==null){
                throw new RuntimeException("汽车不存在");
            }
        }

        //切回数据源源2
        DynamicDataSourceHolder.setDynamicDataSourceKey(CommonConstant.SLAVE);
        Map<String,Object> orderInfo=new HashMap<>();
        orderInfo.put("uid",user.getId());
        orderInfo.put("cid",car.getId());
        orderInfo.put("total",car.getPrice());
        commonMapper.saveOrderInfo(orderInfo);

        return true;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 优化

所以我们希望用AOP来优化上述手动切换数据源的情况,实现思路如下:

  1. 定义一个注解,记录数据源信息。
  2. 编写一个切面,拦截有该注解的类或者方法根据注解的值自动切换数据源。

所以首先编写一个注解,该注解专门记录当前方法或者类用到了数据源的key。

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Ds
{
    /**
     * 切换数据源名称
     */
    public String value() default CommonConstant.MASTER;
}
1
2
3
4
5
6
7
8
9
10
11

然后我们针对注解,编写一个切面,通过获取注解的value决定切换到哪个数据源。

@Aspect
@Component
public class DataSourceAspect {

    // 设置Ds注解的切点表达式,所有Ds都会触发当前环绕通知
    @Pointcut("@annotation(com.example.springdatasource.annotation.Ds)")
    public void dynamicDataSourcePointCut(){

    }

    //环绕通知
    @Around("dynamicDataSourcePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{
        //获取数据源的key
        String key = getDefineAnnotation(joinPoint).value();
        //将数据源设置为该key的数据源
        DynamicDataSourceHolder.setDynamicDataSourceKey(key);
        try {
            return joinPoint.proceed();
        } finally {
            //使用完成后切回master
            DynamicDataSourceHolder.removeDynamicDataSourceKey();
        }
    }

    /**
     * 先判断方法的注解,后判断类的注解,以方法的注解为准
     * @param joinPoint
     * @return
     */
    private Ds getDefineAnnotation(ProceedingJoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Ds dataSourceAnnotation = methodSignature.getMethod().getAnnotation(Ds.class);
        if (Objects.nonNull(methodSignature)) {
            return dataSourceAnnotation;
        } else {
            Class<?> dsClass = joinPoint.getTarget().getClass();
            return dsClass.getAnnotation(Ds.class);
        }
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

这样一来,我们的mapper只需添加一个注解即可完成数据源切换:

@Mapper
public interface CommonMapper {



    @Ds("master")
    User getUserInfo(String id);


    @Ds("slave")
    DataSourceInfo getNewDataSourceInfo(String sourceKey);
    @Ds("slave")
    int saveOrderInfo(Map<String,Object> orderInfo);

    @Ds("slave2")
    Car getCarInfo(String id);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

然后我们的业务代码就可以简化了。

@PostMapping("/orderCar2")
    public boolean orderCar2(@RequestBody Map<String,Object> params) throws SQLException {
        Map<String, Object> map = new HashMap<>();
        //在主库中查询汽车信息列表
        User user = commonMapper.getUserInfo((String) params.get("uid"));
        if (user==null){
            throw new RuntimeException("用户不存在");
        }


        //在从库中查询db3数据源信息
        DataSourceInfo dataSourceInfo = commonMapper.getNewDataSourceInfo("slave2");
        map.put("dataSource", dataSourceInfo);
        log.info("数据源信息:{}", dataSourceInfo);
        //尝试db3的连接是否可用
        DruidDataSource druidDataSource = dataSourceUtil.createDataSourceConnection(dataSourceInfo);



        Car car=null;

        if (Objects.nonNull(druidDataSource)) {
            dataSourceUtil.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDataSourceKey());
            //在新的数据源中查询用户信息
            car = commonMapper.getCarInfo((String) params.get("cid"));
            if (car==null){
                throw new RuntimeException("汽车不存在");
            }
        }

        //切回数据源源2
        Map<String,Object> orderInfo=new HashMap<>();
        orderInfo.put("uid",user.getId());
        orderInfo.put("cid",car.getId());
        orderInfo.put("total",car.getPrice());
        commonMapper.saveOrderInfo(orderInfo);

        return true;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

自此我们就完成了一个只需一个注解解决多数据源问题。

# 小结

我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。

# 参考文献

SpringBoot整合多数据源,动态添加新数据源并切换(保姆级教程):https://juejin.cn/post/7222186286563737655#heading-5 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
Spring Boot自动装配原理及实践
一文快速上手Sharding-JDBC

← Spring Boot自动装配原理及实践 一文快速上手Sharding-JDBC→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×