当前位置: 首页 > news >正文

MyBatis-Plus 动态分表实战:基于 DynamicTableNameInnerInterceptor 的多端数据隔离方案

MyBatis-Plus 动态分表实战:基于 DynamicTableNameInnerInterceptor 的多端数据隔离方案

一、概述

本文基于MyBatis-PlusDynamicTableNameInnerInterceptor实现动态分表,核心思路是:在 SQL 执行前,通过 ThreadLocal 传递表名后缀,拦截器自动将逻辑表名替换为物理表名,从而实现多端数据隔离(如端 At_product_a、端 Bt_product_b)。

二、核心类职责

职责
ShardInterceptorConfig配置类,注册拦截器并绑定 Handler
TableNameShardHandler拦截器回调,执行表名拼接
ShardContextHelperThreadLocal 工具类,存取/清除后缀
ShardTableProperties配置属性,定义默认后缀值

以上类名均为示意,实际项目中按自身命名规范调整。

三、调用链路

业务方法 → setShardType(type) → ShardContextHelper.setShardSuffix(suffix) ↓ (ThreadLocal 存储) MyBatis 执行 SQL ↓ DynamicTableNameInnerInterceptor 拦截 ↓ TableNameShardHandler.dynamicTableName(sql, tableName) ↓ ShardContextHelper.getShardSuffix() ↓ tableName + "_" + suffix → 实际物理表名 ↓ 业务方法 finally → clearShardType()

四、各层详细逻辑

4.1 配置层 —ShardInterceptorConfig

@Bean("shardingMybatisPlusInterceptor")publicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();DynamicTableNameInnerInterceptordynamicTableNameInnerInterceptor=newDynamicTableNameInnerInterceptor();dynamicTableNameInnerInterceptor.setTableNameHandler(newTableNameShardHandler());interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);returninterceptor;}
  • 创建DynamicTableNameInnerInterceptor,设置全局唯一的TableNameShardHandler
  • 该拦截器会拦截所有 SQL,对每张表都调用 Handler

4.2 拦截层 —TableNameShardHandler

@OverridepublicStringdynamicTableName(Stringsql,StringtableName){returntableShard(tableName);}publicStringtableShard(StringtableName){Stringsuffix=ShardContextHelper.getShardSuffix();if(ObjectUtil.isNotEmpty(suffix)){returntableName+"_"+suffix;}returntableName;}
  • 从 ThreadLocal 读取后缀,非空则拼接原表名_后缀
  • 后缀为空时原样返回,即不设置后缀的查询走原表

4.3 上下文传递层 —ShardContextHelper

privatestaticThreadLocal<String>shardSuffix=newThreadLocal<>();publicstaticvoidsetShardSuffix(Stringsuffix)// 设置publicstaticStringgetShardSuffix()// 获取publicstaticvoidclearShardSuffix()// 清除
  • 使用ThreadLocal保证线程隔离,不同请求可路由到不同分表
  • 提供 set/get/clear 三段式操作

4.4 配置属性层 —ShardTableProperties

privateStringtableTypes="a,b";// 支持的分表类型privateStringdefaultType="a";// 默认后缀
  • defaultType:默认后缀为"a",即未指定 type 时默认查端 A 分表
  • tableTypes:声明了支持的分表类型列表

五、业务使用模式

所有业务类遵循统一的try-finally 模式

publicList<XxxResponse>someMethod(Stringtype){try{// 1. 确定后缀:未传则用默认值if(StringUtils.isBlank(type)){type=shardTableProperties.getDefaultType();}// 2. 设置 ThreadLocalsetShardType(type);// 3. 执行数据库操作(此时 SQL 中的表名已被拦截器替换)...}finally{// 4. 清除 ThreadLocal,防止线程复用导致污染clearShardType();}}

适用场景示例

场景分表用途
运营内容管理不同端的内容隔离(tabs/files/guides)
行业列表按端隔离行业数据
产品列表按端隔离产品数据
资源文件按端隔离文件资源
收藏功能按端隔离用户收藏

六、分表效果示例

假设逻辑表名为t_product

type 参数ThreadLocal 后缀实际查询表
"a"at_product_a
"b"bt_product_b
null/ 空a(默认)t_product_a

七、设计特点

  1. 线程安全:基于 ThreadLocal,每个请求线程独立,互不干扰
  2. 对业务代码侵入小:只需在业务方法前后 set/clear,Mapper 层无感知
  3. 灵活路由:通过请求参数动态决定查哪张分表,支持多端数据隔离
  4. 防御性编程: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”(阴影)等拼写错误混入。

http://www.cnnetsun.cn/news/4141299.html

相关文章:

  • 三步保存视频号视频:免费开源资源嗅探下载工具的完整上手指南
  • LenovoLegionToolkit 电源模式不同步?30秒自检 + 完整修复流程
  • C++ Vector核心解析与面试高频考点实战
  • 威海教师评职称需要满足哪些条件?2026年最新政策解读
  • 2026四大AI论文写作软件深度横评|从降重到润色,各有所长别盲选
  • 网络资源如何3步抓取?res-downloader 完整实战指南
  • Spring Boot电脑硬件资产管理系统:从零部署到全流程实战
  • 手机怎么把 Kimi 对话导出,AI 导出鸭适配移动端一键完整导出对话记录,对比多种转换方式选出高效操作办法
  • sklearn逻辑回归实战:TF-IDF文本分类全流程解析与调优指南
  • AI Agent 面试题 387:Agent的工作记忆在多步推理中扮演什么角色?
  • 后端开发者指南:用LangGraph构建可控AI工作流与多智能体系统
  • 考研复试准备全攻略:专业复习与面试技巧
  • SPT-AKI 存档编辑器:13 项功能与运行要求
  • KMS_VL_ALL_AIO完整教程:3分钟免费激活Windows和Office
  • 网盘直链下载助手教程:免费脚本 3 分钟装好,8 大网盘一键取直链
  • YDWE:魔兽争霸3地图编辑器二次开发,给War3地图作者的手艺活装上Lua
  • 毕业论文格式难题终结:MathType安装、目录样式与图片显示的底层逻辑与系统解决方案
  • PCL2启动器全攻略:从零搭建Minecraft模组光影环境
  • Video2X 使用手册:把模糊老视频放大到 4K、把 30 帧补成 60 帧,一次讲透
  • 告别终端多开:从Tmux到IDE集成,构建高效命令行工作流
  • IPv6 Toolkit 完整指南:面向 IPv6 网络安全评估与故障排查的命令行工具包
  • 抖音下载器教程:3步搞定无水印下载,批量保存创作者全部作品
  • 麻将游戏开发框架:majiang-cocos-creator 如何用 Cocos Creator 搭出完整牌局
  • 一文读懂用户脚本如何绕过视频网站年龄限制:前端绕过机制深度解析
  • 跳出AI模型期望的享乐跑步机:从追逐新模型到榨取现有价值
  • NAppGUI资源编译器nrc详解:图片、文本、多语言消息一键打包进可执行文件
  • AI Agent工具调用治理:密码学绑定与可复现性验证实战
  • 揭秘“逆天特性8”:AI与云原生如何重塑现代开发工作流
  • 蓝桥杯国赛真题解析:next_permutation与模拟实现排列波动值计算
  • 再倔的窗口也听你的:Window Resizer 强制调整窗口大小,精确到 1 像素