核心内容摘要
C++20模板实战tuple展开入参
分库分表本质就是在一次 SQL 执行前动态决定用哪个数据库连接DataSource用哪张真实表table_xx而MyBatis / MyBatis-Plus 本身并不具备分库分表能力真正做到“动态切换”的是拦截器 路由规则 ThreadLocal 上下文。
在 SQL 真正发送到数据库之前通过拦截器计算路由规则动态替换 DataSource 和表名。
ORM 框架https://gitee.com/laomaodu/orm-framework分库分库并不是运行时创建数据库连接而是系统启动时初始化多个 DataSource执行 SQL 时通过 AbstractRoutingDataSource 根据 ThreadLocal 中的路由 key 动态选择目标 DataSource从对应的连接池中获取连接。
1️⃣ 多数据源准备前提spring:datasource:db0: ...db1: ...db2: ...系统启动时所有 DataSource 都初始化放入一个 Map 中MapString, DataSource dataSourceMap;public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); } }每次 SQL 执行前Spring 会调用determineCurrentLookupKey()返回值决定使用哪个 DataSourceThreadLocal 保存“当前库”public class DataSourceContext { private static final ThreadLocalString HOLDER new ThreadLocal(); public static void set(String dbKey) { HOLDER.set(dbKey); } public static String get() { return HOLDER.get(); } }4️⃣ 在执行前设置库String dbKey db (userId %
;DataSourceContext.set(dbKey);Bean public DataSource dataSource() { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(db0, dataSource0()); targetDataSources.put(db1, dataSource1()); DynamicDataSource ds new DynamicDataSource(); ds.setDefaultTargetDataSource(dataSource0()); ds.setTargetDataSources(targetDataSources); return ds; }public class DynamicDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); } } public class DataSourceContext { private static final ThreadLocalString HOLDER new ThreadLocal(); public static void set(String key) { HOLDER.set(key); } public static String get() { return HOLDER.get(); } public static void clear() { HOLDER.remove(); } }//
分库规则 String dbKey db (userId %
; DataSourceContext.set(dbKey);MyBatis ↓ DynamicDataSource.getConnection() ↓ determineCurrentLookupKey() ↓ DataSourceContext.get() → db1 ↓ targetDataSources.get(db
↓ db1DataSource.getConnection() ↓ 从 db1 的连接池拿 Connection分表分表是如何“动态切换表名”的select * from order where id ?MyBatis 最终会生成BoundSqlString sql boundSql.getSql();Intercepts({ Signature( type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) }) public class ShardingInterceptor implements Interceptor { }long userId getUserId(param); String table order_ (userId %
;///方法2 SQLParser.parse(sql).replaceTable();完整一次执行流程串起来
Mapper 方法调用
分库分表拦截器触发
从参数中取分片键userId / orderId
计算 - dbKey userId % 2 - table order_ (userId %
16)
ThreadLocal 设置 dbKey
SQL 中 order → order_xx
Executor 使用正确 DataSource
JDBC 执行最终 SQL为什么必须用 ThreadLocal一个请求 一个线程同一线程内多次 SQL必须走同一个库ThreadLocal无侵入自动隔离这是分库分表的线程级上下文基础ShardingSphere它内置了事务传播和多数据源管理。
手动实现容易错。
ShardingSphere JDBC 本质上是一个增强版 DataSource在 SQL 执行前通过解析 SQL 和分片算法计算路由结果动态选择目标数据源并重写 SQL这与手写 AbstractRoutingDataSource 的原理完全一致只是做了工程级封装。
MyBatis↓ShardingSphereDataSource ←等价于你的 DynamicDataSource↓真实 DataSourcedb0 / db1↓MySQL刚刚手写的ShardingSphere 对应DynamicDataSourceShardingSphereDataSourceThreadLocalSQL Hint / 内部上下文分库算法ShardingAlgorithmSQL replaceSQL Rewrite EnginedependencygroupIdorg.apache.shardingsphere/groupIdartifactIdshardingsphere-jdbc-core-spring-boot-starter/artifactIdversion
5.