
MyBatis-Plus 动态分表实战基于 DynamicTableNameInnerInterceptor 的多端数据隔离方案一、概述本文基于MyBatis-PlusDynamicTableNameInnerInterceptor实现动态分表核心思路是在 SQL 执行前通过 ThreadLocal 传递表名后缀拦截器自动将逻辑表名替换为物理表名从而实现多端数据隔离如端 At_product_a、端 Bt_product_b。二、核心类职责类职责ShardInterceptorConfig配置类注册拦截器并绑定 HandlerTableNameShardHandler拦截器回调执行表名拼接ShardContextHelperThreadLocal 工具类存取/清除后缀ShardTableProperties配置属性定义默认后缀值以上类名均为示意实际项目中按自身命名规范调整。三、调用链路业务方法 → setShardType(type) → ShardContextHelper.setShardSuffix(suffix) ↓ (ThreadLocal 存储) MyBatis 执行 SQL ↓ DynamicTableNameInnerInterceptor 拦截 ↓ TableNameShardHandler.dynamicTableName(sql, tableName) ↓ ShardContextHelper.getShardSuffix() ↓ tableName _ suffix → 实际物理表名 ↓ 业务方法 finally → clearShardType()四、各层详细逻辑4.1 配置层 —ShardInterceptorConfigBean(shardingMybatisPlusInterceptor)publicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptornewMybatisPlusInterceptor();DynamicTableNameInnerInterceptordynamicTableNameInnerInterceptornewDynamicTableNameInnerInterceptor();dynamicTableNameInnerInterceptor.setTableNameHandler(newTableNameShardHandler());interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);returninterceptor;}创建DynamicTableNameInnerInterceptor设置全局唯一的TableNameShardHandler该拦截器会拦截所有 SQL对每张表都调用 Handler4.2 拦截层 —TableNameShardHandlerOverridepublicStringdynamicTableName(Stringsql,StringtableName){returntableShard(tableName);}publicStringtableShard(StringtableName){StringsuffixShardContextHelper.getShardSuffix();if(ObjectUtil.isNotEmpty(suffix)){returntableName_suffix;}returntableName;}从 ThreadLocal 读取后缀非空则拼接原表名_后缀后缀为空时原样返回即不设置后缀的查询走原表4.3 上下文传递层 —ShardContextHelperprivatestaticThreadLocalStringshardSuffixnewThreadLocal();publicstaticvoidsetShardSuffix(Stringsuffix)// 设置publicstaticStringgetShardSuffix()// 获取publicstaticvoidclearShardSuffix()// 清除使用ThreadLocal保证线程隔离不同请求可路由到不同分表提供 set/get/clear 三段式操作4.4 配置属性层 —ShardTablePropertiesprivateStringtableTypesa,b;// 支持的分表类型privateStringdefaultTypea;// 默认后缀defaultType默认后缀为a即未指定 type 时默认查端 A 分表tableTypes声明了支持的分表类型列表五、业务使用模式所有业务类遵循统一的try-finally 模式publicListXxxResponsesomeMethod(Stringtype){try{// 1. 确定后缀未传则用默认值if(StringUtils.isBlank(type)){typeshardTableProperties.getDefaultType();}// 2. 设置 ThreadLocalsetShardType(type);// 3. 执行数据库操作此时 SQL 中的表名已被拦截器替换...}finally{// 4. 清除 ThreadLocal防止线程复用导致污染clearShardType();}}适用场景示例场景分表用途运营内容管理不同端的内容隔离tabs/files/guides行业列表按端隔离行业数据产品列表按端隔离产品数据资源文件按端隔离文件资源收藏功能按端隔离用户收藏六、分表效果示例假设逻辑表名为t_producttype 参数ThreadLocal 后缀实际查询表aat_product_abbt_product_bnull/ 空a默认t_product_a七、设计特点线程安全基于 ThreadLocal每个请求线程独立互不干扰对业务代码侵入小只需在业务方法前后 set/clearMapper 层无感知灵活路由通过请求参数动态决定查哪张分表支持多端数据隔离防御性编程finally 块确保 ThreadLocal 清除避免线程池复用污染八、潜在问题8.1 全局拦截无差别替换DynamicTableNameInnerInterceptor会对所有表执行 Handler包括不需要分表的表。当 ThreadLocal 有值时所有表名都会被拼接后缀若某张表不存在对应的分表则会报错。当前依赖不设后缀则不替换来规避但一旦忘记 clear 或在嵌套调用中设置可能误伤其他表。8.2 异步线程丢失上下文如果业务方法中使用了CompletableFuture异步并发查询子线程无法继承父线程的 ThreadLocal 值。若主线程未设置分表后缀就提交了异步任务子线程中的查询可能走错表或走原表。8.3 set/clear 重复散落每个业务类都重复编写了setShardType()/clearShardType()方法且 try-finally 模板代码大量重复可抽取为模板方法或 AOP 切面统一管理。8.4 命名规范方法名中 “Shard” 拼写需统一Shard 分片避免 “Shading”阴影等拼写错误混入。