核心是threadlocal存数据源标识、abstractroutingdatasource路由分发:threadlocal安全存储key,determinecurrentlookupkey()返回该key,aop切面自动注入@ds注解值,sqlsessionfactorybean和transactionmanager须基于dynamicdatasource配置。

核心是让每个线程在执行 SQL 前,明确知道自己该用哪个数据源,AbstractRoutingDataSource 负责“选”,ThreadLocal 负责“记”——把当前线程要选的 key 安全地存起来,不被其他线程干扰。
定义线程级数据源标识容器
用 ThreadLocal 存一个字符串(比如 "master" 或 "slave01"),确保每次数据库操作前都能快速拿到它:
- 声明为
private static final ThreadLocal<string> CONTEXT_HOLDER = new ThreadLocal();</string> - 提供
setDataSource(String key)方法写入标识 - 提供
getDataSource()方法读取标识,返回 null 表示未设置 - 建议在请求结束或事务完成后调用
remove(),避免线程复用导致脏数据
继承 AbstractRoutingDataSource 并重写路由逻辑
这个类不直接连接数据库,只做“分发”工作。关键就是实现 determineCurrentLookupKey():
- 方法体只需一行:
return CONTEXT_HOLDER.get(); - 构造时传入所有真实数据源(如
master、slave1、slave2)和默认数据源 - 调用
super.setTargetDataSources(targetDataSources)注册可用数据源 map,key 必须与 ThreadLocal 中写的完全一致 - 必须调用
super.afterPropertiesSet()触发内部初始化
通过注解 + AOP 自动注入数据源标识
避免在业务代码里手动 set/remove,统一由切面处理:
- 自定义注解
@DS("slave"),作用在 Service 方法上 - AOP 切面在方法执行前获取该注解的 value,调用
DbContextHolder.setDataSource(value) - 使用
@Around并在 finally 块中remove(),保证清理不遗漏 - 若方法没加注解,默认走 ThreadLocal 中的 null → AbstractRoutingDataSource 会自动 fallback 到默认数据源
确保 MyBatis 和事务正确接入
动态数据源只是“路由器”,下游组件必须认它:
-
SqlSessionFactoryBean的dataSource属性必须指向你写的DynamicDataSource实例,不能直接配 Druid 或 Hikari -
PlatformTransactionManager也要基于DynamicDataSource构建,否则@Transactional可能跨库失效 - 如果用了 MyBatis-Plus,注意其内置的多数据源 starter(如 dynamic-datasource-spring-boot-starter)已封装好上述逻辑,可直接替代手写方案
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











