关键在于用threadlocal绑定当前线程与库别名,配合abstractroutingdatasource实现动态数据源路由:通过上下文类管理threadlocal值,aop或拦截器完成捕获与写入,determinecurrentlookupkey()实时返回别名,确保线程私有、隔离、可追溯。

关键在于用 ThreadLocal 绑定当前线程与库别名,配合 AbstractRoutingDataSource 的路由机制实现私有、隔离、可追溯的动态锁定。
ThreadLocal 存储库别名是核心前提
每个请求或业务操作都在独立线程中执行。要确保“这个线程只走这个库”,就必须在线程启动初期就写入唯一标识——比如租户ID、环境编码、读写类型等生成的库别名(如 "tenant_007" 或 "slave_read_v2")。
- 定义一个上下文持有类,内部用
ThreadLocal<string></string>管理别名 - 提供
set(String key)、get()、clear()三方法,严格配对调用 - 务必在请求结束(如 Filter 或 AOP after)调用
clear(),否则可能污染后续请求
AbstractRoutingDataSource 依赖该变量做实时路由
自定义数据源类继承 AbstractRoutingDataSource,重写 determineCurrentLookupKey() 方法——它不自己决策,只忠实地返回 ThreadLocal.get() 的值。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
- Spring 每次获取连接时都会触发此方法,因此只要 ThreadLocal 中有值,就能精准命中目标数据源
- 若返回
null或未注册的 key,会 fallback 到defaultTargetDataSource,所以默认库必须配置且可用 - 注意:
targetDataSourcesMap 是容器启动时解析并固化为resolvedDataSources的,运行时新增数据源需额外扩展(如支持动态注册)
在业务入口处完成“捕获—写入—锁定”闭环
所谓“捕获”,是指从外部明确输入中提取库别名;“锁定”不是加锁,而是将该别名稳稳塞进当前线程的 ThreadLocal。
- 可通过 Controller 参数(如
@RequestParam("db") String dbKey)、Header(如X-Tenant-ID)、JWT Claim 或方法注解(如@TargetDB("tenant_007"))等方式获取别名 - 建议统一由 AOP 切面或 WebMvcConfigurer 注册的 HandlerInterceptor 完成写入,避免各处重复逻辑
- 示例:AOP 在目标方法执行前调用
DataSourceContextHolder.set(dbKey),执行后调用clear()
验证与调试时关注线程粒度和生命周期
别名是否真正“私有”,取决于它是否仅对当前线程可见、是否随线程消亡而释放。
- 不要在异步线程(如
@Async、线程池 submit)中直接复用主线程的 ThreadLocal 值——需显式传递并手动 set - 可在关键节点(如 DAO 执行前)打印
DataSourceContextHolder.get(),确认值存在且正确 - 结合日志 MDC,把库别名注入 log pattern(如
%X{dbKey}),便于全链路追踪










