多数据源切换:@DS 注解底层调用原理深度剖析

发布时间:2026/9/15 11:17:50

多数据源切换:@DS 注解底层调用原理深度剖析 多数据源切换DS 注解底层调用原理深度剖析一、概述DS注解来自dynamic-datasource-spring-boot-starter组件苞米豆出品并非 MyBatis-Plus 核心包而是其生态扩展。该注解用于在多数据源场景下声明式地指定当前方法/类使用哪个数据源。使用示例DS(xxx)// 切换到指定数据源Transactional(propagationPropagation.REQUIRES_NEW)publicclassOrderDaoImplextendsServiceImplOrderMapper,OrderEntity{// ...}二、核心组件与调用链2.1 整体架构DS 注解 │ ▼ DynamicDataSourceAnnotationInterceptor (AOP 拦截器) │ ▼ DynamicDataSourceClassResolver (解析注解确定数据源名称) │ ▼ DynamicDataSourceContextHolder (ThreadLocal 存储当前数据源标识) │ ▼ DynamicRoutingDataSource (继承 AbstractRoutingDataSource路由数据源) │ ▼ DataSourceProperty → HikariCP/Druid (实际物理数据源连接池)2.2 六大核心组件组件职责DS注解标记目标数据源名称DynamicDataSourceAnnotationInterceptorAOP MethodInterceptor拦截被DS标注的方法DynamicDataSourceClassResolver解析DS注解处理方法级/类级优先级DynamicDataSourceContextHolder基于 ThreadLocal 持有当前线程的数据源标识DynamicRoutingDataSource继承AbstractRoutingDataSource根据标识路由到真实数据源DataSourceProperty封装每个数据源的配置url、username、pool 等三、底层调用原理详解3.1 自动配置阶段dynamic-datasource-spring-boot-starter通过 Spring Boot 自动配置机制注册核心 Beanspring.factories / AutoConfiguration │ ├── DynamicDataSourceAutoConfiguration │ ├── 注册 DynamicDataSourceProperties读取 spring.datasource.dynamic.* 配置 │ ├── 注册 DynamicRoutingDataSource主数据源 Bean替换默认 DataSource │ ├── 注册 DynamicDataSourceAnnotationAdvisorAOP 切面 │ └── 注册各个数据源连接池master、slave、自定义等 │ └── DynamicDataSourceAopConfiguration └── 注册 DynamicDataSourceAnnotationInterceptor 切点关键点DynamicRoutingDataSource替换了 Spring 默认的DataSourceBean所有数据库操作都经过它路由。3.2 AOP 拦截阶段当调用被DS标注的方法时执行流程如下1. 调用方调用 DS 标注的方法 │ 2. DynamicDataSourceAnnotationInterceptor.invoke() │ 3. DynamicDataSourceClassResolver.findKey(method, targetClass) │ ┌─────────────────────────────────────────────┐ │ │ 注解查找优先级从高到低 │ │ │ ① 方法上的 DS │ │ │ ② 类上的 DS │ │ │ ③ 接口方法上的 DS │ │ │ ④ 接口类上的 DS │ │ │ ⑤ 默认数据源master │ │ └─────────────────────────────────────────────┘ │ 4. DynamicDataSourceContextHolder.push(key) │ 将数据源标识压入 ThreadLocal 栈支持嵌套切换 │ 5. 执行实际业务方法 → MyBatis 执行 SQL │ 6. DynamicRoutingDataSource.determineTargetDataSource() │ 从 ThreadLocal 取出当前数据源标识 │ 根据标识从 MapString, DataSource 中获取对应数据源 │ 7. 方法执行完毕 │ 8. DynamicDataSourceContextHolder.poll() │ 弹出栈顶数据源标识恢复上一层数据源3.3 核心源码逻辑1AOP 拦截器publicclassDynamicDataSourceAnnotationInterceptorimplementsMethodInterceptor{OverridepublicObjectinvoke(MethodInvocationinvocation)throwsThrowable{// 1. 解析当前方法/类上的 DS 注解获取数据源名称StringdsKeydynamicDataSourceClassResolver.findKey(invocation.getMethod(),invocation.getThis().getClass());// 2. 将数据源标识压入 ThreadLocalDynamicDataSourceContextHolder.push(dsKey);try{// 3. 执行实际方法此时 SQL 执行会走路由数据源returninvocation.proceed();}finally{// 4. 方法结束后弹出数据源标识恢复上层DynamicDataSourceContextHolder.poll();}}}2ThreadLocal 上下文持有者publicclassDynamicDataSourceContextHolder{// 使用栈结构支持嵌套 DS 调用privatestaticfinalThreadLocalDequeStringLOOKUP_KEY_HOLDERThreadLocal.withInitial(ArrayDeque::new);publicstaticvoidpush(Stringds){LOOKUP_KEY_HOLDER.get().push(ds);}publicstaticvoidpoll(){DequeStringdequeLOOKUP_KEY_HOLDER.get();deque.poll();if(deque.isEmpty()){LOOKUP_KEY_HOLDER.remove();}}publicstaticStringpeek(){returnLOOKUP_KEY_HOLDER.get().peek();}}栈结构的意义支持嵌套调用场景。例如方法 A 使用DS(master)其内部调用方法 B 使用DS(slave)执行 B 时压入 “slave”B 结束后弹出恢复 “master”。3路由数据源publicclassDynamicRoutingDataSourceextendsAbstractRoutingDataSource{// 存储所有数据源实例privateMapString,DataSourcedataSourceMap;OverrideprotectedObjectdetermineCurrentLookupKey(){// 从 ThreadLocal 获取当前数据源标识returnDynamicDataSourceContextHolder.peek();}OverrideprotectedDataSourcedetermineTargetDataSource(){StringlookupKey(String)determineCurrentLookupKey();DataSourcedataSourcedataSourceMap.get(lookupKey);if(dataSourcenull){thrownewDataSourceNotFoundException(数据源 [lookupKey] 未找到);}returndataSource;}}4注解解析器publicclassDynamicDataSourceClassResolver{publicStringfindKey(Methodmethod,Class?targetClass){// 优先级方法 类 接口方法 接口类// 1. 检查方法上的 DSDSdsmethod.getAnnotation(DS.class);if(ds!null)returnds.value();// 2. 检查类上的 DSdstargetClass.getAnnotation(DS.class);if(ds!null)returnds.value();// 3. 检查接口方法上的 DS// 4. 检查接口类上的 DS// ...// 5. 返回默认数据源returnmaster;}}四、与Transactional的协同与冲突4.1 典型用法DS(warehouse)Transactional(propagationPropagation.REQUIRES_NEW)publicclassOrderDaoImplextendsServiceImpl...{}4.2 执行顺序问题Spring 事务管理器和DSAOP 都基于代理执行顺序取决于Order顺序效果事务 AOP 先执行Order更小事务先绑定默认数据源的 ConnectionDS切换无效DSAOP 先执行Order更小先切换数据源事务在正确数据源上开启dynamic-datasource-spring-boot-starter默认将DS的 AOP 优先级设为Ordered.HIGHEST_PRECEDENCE确保先切换数据源再开启事务。4.3REQUIRES_NEW的作用Propagation.REQUIRES_NEW表示始终开启新事务挂起外层事务。结合DS使用时外层事务使用数据源 A进入OrderDaoImpl时DS先切换到warehouse数据源REQUIRES_NEW在warehouse数据源上开启新事务方法结束后新事务提交数据源恢复外层事务继续如果不用REQUIRES_NEW而用默认的REQUIRED外层事务已绑定了 master 数据源的 ConnectionDS切换可能不生效因为事务同步管理器已绑定 Connection。五、完整调用时序图Caller │ ├─① 调用 DS(warehouse) 标注的方法 │ ▼ DynamicDataSourceAnnotationInterceptor.invoke() │ ├─② findKey() → 解析 DS 注解 → 返回 warehouse │ ├─③ DynamicDataSourceContextHolder.push(warehouse) │ ThreadLocal 栈: [warehouse] │ ├─④ Spring TransactionInterceptor.invoke() │ │ │ ├─⑤ DynamicRoutingDataSource.determineTargetDataSource() │ │ 从 ThreadLocal 取 warehouse → 返回对应 DataSource │ │ │ ├─⑥ 从 warehouse DataSource 获取 Connection │ │ │ ├─⑦ 开启事务 (REQUIRES_NEW → 新事务) │ │ │ ├─⑧ 执行 MyBatis SQL → 使用 warehouse 连接 │ │ │ └─⑨ 提交/回滚事务 │ ├─⑩ DynamicDataSourceContextHolder.poll() │ ThreadLocal 栈: [] (恢复为空) │ └─⑪ 返回结果六、关键设计要点总结要点说明AOP 拦截基于MethodInterceptor拦截DS标注的方法ThreadLocal 栈使用栈结构存储数据源标识支持嵌套调用场景AbstractRoutingDataSource继承 Spring 的路由数据源抽象类运行时动态选择数据源注解优先级方法级 类级 接口方法级 接口类级 默认 masterAOP 顺序DS优先级最高确保先切换数据源再开启事务事务配合需注意Transactional与DS的 AOP 顺序REQUIRES_NEW可避免事务绑定冲突连接池隔离每个数据源拥有独立的连接池HikariCP/Druid互不影响七、数据源配置参考定义数据源常量publicclassDsName{publicstaticfinalStringMASTERmaster;// 主库publicstaticfinalStringSLAVEslave;// 只读库publicstaticfinalStringSTAR_ROCKSstarrocks;// StarRocks 分析库publicstaticfinalStringWAREHOUSEwarehouse;// 数据仓库}对应application.yml配置spring:datasource:dynamic:primary:masterdatasource:master:url:jdbc:mysql://host:3306/db_mainusername:xxxpassword:xxxslave:url:jdbc:mysql://host:3306/db_slavestarrocks:url:jdbc:mysql://host:9030/db_analyticswarehouse:url:jdbc:mysql://host:3306/db_warehouse八、常见问题与最佳实践8.1DS不生效的常见原因原因解决方案同类内部方法调用绕过代理将DS方法抽到独立 Bean或使用AopContext.currentProxy()TransactionalAOP 优先级高于DS确认dynamic-datasource版本 ≥ 3.x或手动指定Order在非 Spring 管理的线程中使用手动调用DynamicDataSourceContextHolder.push()/poll()DS标注在 private 方法上AOP 无法拦截 private 方法需改为 public 或提升到类级别8.2 最佳实践DS优先标注在类上减少遗漏整个类默认使用指定数据源方法级DS覆盖类级个别需要切换数据源的方法单独标注配合REQUIRES_NEW使用跨数据源调用时避免事务绑定冲突避免同类内部调用确保 AOP 代理生效嵌套DS调用栈结构自动处理无需手动管理
延伸阅读

更多相关文章

2026/9/12 13:07:05

全球气候治理新框架:公正转型与多边行动

1. 演讲背景与核心议题解析 2023年联合国气候行动峰会上,助理秘书长哈特的演讲引发了国际社会的广泛关注。这份长达45分钟的政策声明,系统阐述了当前全球气候治理面临的三大核心挑战:加速气候行动落地、捍卫多边合作机制、推动公正能源转型。…

2026/9/12 2:54:35

主流Claw工具横向评测与选购指南

1. 项目概述:主流Claw工具横向评测最近在技术社区里关于各种Claw工具的讨论越来越热,作为一个长期关注效率工具的老用户,我花了三周时间深度体验了市面上主流的5款大厂Claw产品。从安装配置到日常使用,从基础功能到高阶玩法&#…

2026/9/7 4:30:43

Python数据驱动接口自动化测试:基于Excel与Pandas的实战框架构建

1. 项目概述:从面试题到实战方案的跨越最近在帮团队优化接口自动化测试流程,正好看到网上流传着一道字节跳动的春招面试题,核心是考察如何利用Python和Excel来处理测试数据,进而完成接口自动化。这题目出得挺有意思,它…

2026/9/15 11:17:22

CesiumJS 构建产物 IIFE、ESM 与 CJS 三种发行格式怎么选?

CesiumJS 构建产物 IIFE、ESM 与 CJS 三种发行格式怎么选? 【免费下载链接】cesium An open-source JavaScript library for world-class 3D globes and maps :earth_americas: 项目地址: https://gitcode.com/GitHub_Trending/ce/cesium 把 CesiumJS 接入自…

2026/9/15 11:12:21

联邦学习结合知识蒸馏,解决入侵检测Non-IID数据难题

简介:这是一份基于联邦学习与知识蒸馏的网络入侵检测模型完整源码包,面向计算机、数学、电子信息等专业学生,可用于课程设计、期末大作业或毕业设计参考。项目在NSL-KDD数据集上完成验证,代码同时包含服务端与客户端协同训练框架、…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码