手写数据库路由框架的核心是通过@ShardBy注解标记分片键与策略,经AOP解析注入ThreadLocal上下文,由AbstractRoutingDataSource和MyBatis插件分别实现数据源与表名的动态路由。

用自定义注解标记分片键和路由策略
在手写数据库路由框架时,核心是让每次 DAO 方法调用能感知“该走哪个分片”。最自然的方式是在方法或参数上加注解,比如 @ShardBy,声明分片依据字段名(如 userId)或表达式(如 "#user.id % 4")。这个注解不参与业务逻辑,只作元数据标记,后续由 AOP 或代理层解析。
示例:
@Mapper
public interface OrderMapper {
@ShardBy("orderId")
Order selectById(@Param("orderId") Long orderId);
<pre class='brush:java;toolbar:false;'>@ShardBy(value = "userId", strategy = "hash-mod-8")
List<Order> listByUserId(@Param("userId") Long userId);}
在 AOP 切面中提取注解并注入上下文
定义一个 @Around 切面,拦截所有带 @ShardBy 的 DAO 方法。切面内做三件事:解析注解、从方法参数中提取实际分片值、将值存入线程本地上下文(如 ThreadLocal<ShardContext>)供后续路由使用。
立即学习“Java免费学习笔记(深入)”;
关键点:
- 用
MethodSignature获取参数名和类型,配合SpelExpressionParser支持表达式求值(如#user.id) - 若参数是对象,通过反射或 Spring 的
BeanWrapper提取字段值 - 把解析出的分片键值 + 策略名封装进
ShardContext,并绑定到当前线程
路由决策层读取上下文选择数据源
在 AbstractRoutingDataSource 的 determineCurrentLookupKey() 方法中,不再硬编码逻辑,而是从 ThreadLocal<ShardContext> 取出上下文,根据策略(如 hash-mod、range、一致性 hash)计算目标库/表名,返回对应的数据源 key(如 "ds_01" 或 "ds_01:order_02")。
注意:
- 执行完后必须清空
ThreadLocal,避免连接泄漏或上下文污染(建议在 finally 块或使用 try-with-resources 风格的上下文管理器) - 支持 fallback 机制:当上下文为空时,可走默认库,或抛出明确异常提示缺失分片信息
配合 MyBatis 插件增强 SQL 表名重写
仅切换数据源还不够——分片表名(如 order_01, order_02)也要动态替换。可在 MyBatis 的 Executor 插件中拦截 StatementHandler.prepare(),从同一 ThreadLocal 中取出分片键值,结合策略生成真实表名,再用正则或 AST 方式改写 SQL 中的表标识符。
例如原始 SQL:SELECT * FROM order WHERE id = ? → 改写为:SELECT * FROM order_03 WHERE id = ?(假设 id=1235 路由到第 3 片)。
不复杂但容易忽略



















