核心是用AOP切面在@Service方法执行前确定数据源,结合AbstractRoutingDataSource实现运行时路由;切点必须在Service层,使用@Around获取方法元信息,按命名规则或@Master/@Slave注解路由,ThreadLocal管理线程上下文,动态数据源需正确集成到Spring生命周期并确保事务与读写分离协同。

核心是用 AOP 切面在方法执行前确定数据源,再结合 AbstractRoutingDataSource 完成运行时路由。不依赖中间件,纯 Spring 生态即可落地。
关键切点选在 Service 层,不是 DAO 或 Mapper
很多项目一开始把切面打在 Mapper 接口上,结果发现 determineCurrentLookupKey() 总是先执行、还没来得及标记读写意图就已选错数据源。根本原因是:Spring 的事务代理和数据源路由都在代理链早期触发,而 DAO 层方法调用时上下文早已固定。
- 切点必须落在
@Service方法上,确保在事务开启前完成数据源决策 - 推荐使用
@Around而非@Before,能拿到真实被调用的方法对象(含名称、注解、参数) - 方法名判断规则要明确:以
get、list、query、count开头的视为读操作;其余默认走主库 - 若需强制查主库(如刚写完立刻查最新状态),加自定义注解
@Master,切面优先识别该注解
数据源上下文必须线程绑定且可重入
切换动作本质是往当前线程存一个 key(比如 "MASTER" 或 "SLAVE"),AbstractRoutingDataSource 在获取连接时读取它。这个上下文不能被子线程继承,也不能因嵌套调用被覆盖。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- 用
ThreadLocal<String>存储当前数据源标识,初始化为"MASTER" - 每次进入切面环绕逻辑前先保存旧值,执行完后 restore,避免异步或内部调用污染
- 提供静态工具方法
DynamicDataSourceContextHolder.set(DataSourceType.SLAVE)和reset(),方便手动控制 - 事务方法内若混用读写,需显式 reset 后再 set,否则从库查询可能误走主库连接池
路由类必须正确集成到 DataSource 生命周期
DynamicDataSource 是个包装器,它本身不建连接,只负责转发请求。它的 targetDataSources 映射里必须包含你配置好的主从真实数据源实例,并设好默认源。
- 在
DataSourceConfig中,通过@Bean注册 masterDataSource 和 slaveDataSource - 构造
DynamicDataSource时传入这两个 bean,同时调用setTargetDataSources(map)和setDefaultTargetDataSource(master) - 确保
SqlSessionFactory和TransactionManager都注入的是这个动态数据源 bean,而不是任一单个数据源 - MyBatis-Plus 的
page()、lambdaQuery().list()等方法才能自动生效,无需改业务代码
事务与读写分离必须协同兜底
事务方法里如果先查后写,又没控制好数据源,极易出现“从库读到旧快照 → 主库写新数据 → 下次查还是旧值”的幻读问题。这不是 AOP 没切对,而是语义冲突。
- 所有
@Transactional方法默认强制走主库,无论方法名是否含 “get” —— 这是安全底线 - 若确需在事务中读从库(极少数报表场景),必须显式加
@Slave注解并关闭事务传播(propagation = NOT_SUPPORTED) - 事务嵌套时,内层方法的数据源选择以最外层事务为准,避免多级切换引发连接泄漏或隔离级别异常
- Druid 或 Hikari 连接池的
removeAbandonedOnMaintenance建议开启,防止因数据源切换残留未归还连接


















