MyBatis sql拦截器实现一个自动根据租户进行分表的方案
需求描述:
在一个多租户系统中,通过 MyBatis 实现动态数据表分离。具体来说,您希望通过 MyBatis 拦截器在执行 SQL 时自动将表名根据当前租户 ID (tenantId) 进行修改。这样,每个租户的数据就可以存储在专属于它们的表中,实现数据隔离。
需求细节:
-
拦截 SQL 语句:使用 MyBatis 的
@Intercepts注解和Interceptor接口来拦截 SQL 语句。 -
修改表名:在 SQL 执行时,根据提供的租户 ID (
tenantId) 动态地修改 SQL 语句中的表名。例如,如果原表名为ALARM_RECORD_INFO,租户 ID 为1001,则修改后的表名应为ALARM_RECORD_INFO_1001。 -
获取租户 ID:从 MyBatis Mapper 接口方法的参数中获取
tenantId。这是通过在 Mapper 接口的方法参数上使用@Param("tenantId")注解实现的。 -
配置:在
mybatis-config.xml文件中配置拦截器,并指定需要根据租户 ID 分表的表名。 -
映射器文件 (
AlarmRecordMapper.xml):定义 SQL 语句,尽管 SQL 中写的是原始表名,实际执行时,表名将根据租户 ID 被拦截器动态修改
实现
下面是实现一个 MyBatis 拦截器的完整代码,该拦截器会拦截 SQL 语句,在执行操作时动态地将表名根据当前租户 ID (tenantId) 进行修改,以实现数据隔离。这个实现包括了从 @Repository 注解的 Mapper 接口中获取 tenantId 参数的逻辑。
拦截器实现 (TableTenantSuffixInterceptor.java)
package com.example;import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class TableTenantSuffixInterceptor implements Interceptor {private Set<String> tableNameSet = new HashSet<>();@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();BoundSql boundSql = statementHandler.getBoundSql();String originalSql = boundSql.getSql();MetaObject metaObject = SystemMetaObject.forObject(statementHandler);Object parameterObject = boundSql.getParameterObject();String tenantId = getTenantId(parameterObject);if (tenantId != null && !tenantId.isEmpty()) {for (String tableName : tableNameSet) {if (originalSql.toUpperCase().contains(tableName.toUpperCase())) {String newTableName = tableName + "_" + tenantId;String newSql = originalSql.replaceAll(Pattern.quote(tableName), newTableName);metaObject.setValue("delegate.boundSql.sql", newSql);break; // Assume only one table name per SQL}}}return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {String tablesByTenant = properties.getProperty("tablesByTenant");if (tablesByTenant != null) {for (String table : tablesByTenant.split(",")) {tableNameSet.add(table.trim());}}}private String getTenantId(Object parameterObject) {if (parameterObject instanceof Map) {Map<?, ?> paramMap = (Map<?, ?>) parameterObject;return (String) paramMap.get("tenantId");} else {MetaObject metaObject = SystemMetaObject.forObject(parameterObject);if (metaObject.hasGetter("tenantId")) {return (String) metaObject.getValue("tenantId");}}return null;}
}
MyBatis 配置文件 (mybatis-config.xml)
在 MyBatis 的配置文件中,注册拦截器并指定需要根据租户 ID 分表的表名。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><plugins><plugin interceptor="com.example.TableTenantSuffixInterceptor"><property name="tablesByTenant" value="ALARM_RECORD_INFO,ALARM_TASK_INFO"/></plugin></plugins><!-- 其他配置,例如 environments, mappers 等 -->
</configuration>
Mapper 接口示例 (AlarmRecordMapper.java)
package com.example;import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Set;@Repository
public interface AlarmRecordMapper {List<AlarmRecordEntity> getAlarmRecordByTaskIds(@Param("tenantId") String tenantId, @Param("taskIds") Set<Integer> taskIds);
}
AlarmRecordMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.AlarmRecordMapper"><select id="getAlarmRecordByTaskIds" resultType="com.example.AlarmRecordEntity">SELECT * FROM ALARM_RECORD_INFOWHERE task_id IN<foreach item="taskId" collection="taskIds" open="(" separator="," close=")">#{taskId}</foreach>AND tenant_id = #{tenantId}</select></mapper>
这个实现中,拦截器 TableTenantSuffixInterceptor 会检查每个执行的 SQL 语句,看它是否包含配置中指定的表名。如果是,它会将表名修改为包含租户 ID 后缀的形式。这个过程依赖于在 SQL 语句中直接使用表名,且假设每个 SQL 语句只涉及一个需要进行租户 ID 后缀处理的表。如果 SQL 语句中包含多个需要处理的表名,或者表名的使用方式更加复杂(例如,动态表名或 SQL 函数中使用表名),则可能需要更复杂的逻辑来正确地替换表名。
相关文章:
MyBatis sql拦截器实现一个自动根据租户进行分表的方案
需求描述: 在一个多租户系统中,通过 MyBatis 实现动态数据表分离。具体来说,您希望通过 MyBatis 拦截器在执行 SQL 时自动将表名根据当前租户 ID (tenantId) 进行修改。这样,每个租户的数据就可以存储在专属于它们的表中…...
TiDB in 2023, 一次简单的回顾丨PingCAP 唐刘
2023 年已经过去,TiDB 经过了一年的迭代,又往前进步了一点点,我们非常自豪的看到,TiDB 正在不断地帮助我们的客户成功,包括但不限于: ○ 首个云原生、分布式、全栈国产化银行核心业务系统投产上线丨TiDB …...
debug - 只要在内存中有显示相关的数据, 就会被CE找到
文章目录 debug - 只要在内存中有显示相关的实际数据, 就会被CE找到概述笔记demo实现demo运行效果用CE查找实际数据地址找到自己的调试点 - 方法1找到自己的调试点 - 方法2打补丁备注END debug - 只要在内存中有显示相关的实际数据, 就会被CE找到 概述 自己写了一个demo, 想验…...
Redis 单个与多节点如何实现分布式锁
分布式锁 在许多环境中,分布式锁是非常有用的原语,在这些环境中,不同的进程必须以互斥的方式操作共享资源。在应对并发问题时,Redis 客户端还可以通过加锁的方式,来控制并发写操作对共享数据的修改,从而保…...
频段划分学习射频知识的意义
一、射频电路设计与低频电路设计的不同点 随着频率提高,相应电磁波的波长与变得可与分立电路元件的尺寸相比拟时,电阻、电容和电感这些元件的电响应,将偏离他们的理想频率特性。以 WIFI 2.4G 频段为例,当频率为 2437MHz࿰…...
Effective Objective-C 学习(四)
掌握GCD及操作队列的使用时机 在执行后台任务时,GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue,它虽然与 GCD 不同,但是却与之相关,开发者可以把操作以 NSOperation 子类的形式放在队列中,而这些操作也…...
欢迎来到IT时代----盘点曾经爆火全网的计算机电影
计算机专业必看的几部电影 计算机专业必看的几部电影,就像一场精彩的编程盛宴!《黑客帝国》让你穿越虚拟世界,感受高科技的魅力;《社交网络》揭示了互联网巨头的创业之路,《源代码》带你穿越时间解救世界,这…...
光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表
光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表 文章目录 光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表前言一、可视化工具有哪些?二、那如何做出正确可视化图表 ?GLAD原则1.G原则2.L原则3.A原则4.D原则 三、总结最后…...
如何设计出用于喜欢的界面
要设计出用户喜欢的界面,你可以考虑以下几个方面: 用户研究:首先要了解用户的需求和偏好。你可以通过用户调研、用户访谈和数据分析来获取这些信息。了解用户的行为模式、喜好和痛点,有助于设计出更吸引人的界面。 直观的布局&am…...
第三篇【传奇开心果系列】Python的文本和语音相互转换库技术点案例示例:pyttsx3实现语音助手经典案例
传奇开心果短博文系列 系列短博文目录Python的文本和语音相互转换库技术点案例示例系列 短博文目录一、项目背景和目标二、雏形示例代码三、扩展思路介绍四、与其他库和API集成示例代码五、自定义语音示例代码六、多语言支持示例代码七、语音控制应用程序示例代码八、文本转语音…...
JS中数组的常用方法
concat() 连接两个或更多的数组,并返回结果。 let array1 [1, 2, 3]; let array2 [4, 5, 6]; let concatenatedArray array1.concat(array2); console.log(concatenatedArray); // [1, 2, 3, 4, 5, 6]join() 把数组的所有元素放入一个字符串。元素通过指定…...
最好用的论文检索网站
网站展示: 网站链接 sci-hub文献检索 用途: 可以用文章的DOI来检索并下载文章...
AI专题:AI巨轮滚滚向前
今天分享的是电子系列深度研究报告:《AI专题:AI巨轮滚滚向前》。 (报告出品方:方正证券) 报告共计:65页 来源:人工智能学派 Gemini 1.5 Pro 性能显著增强,长上下文理解取得突破 …...
SpringBoot常见问题
1 引言 Spring Boot是一个基于Spring框架的快速开发脚手架,它简化了Spring应用的初始化和搭建过程,提供了众多便利的功能和特性,比如自动配置、嵌入式Tomcat等,让开发人员可以更加专注于业务逻辑的实现。 Spring Boot还提供了…...
五种多目标优化算法(MOAHA、MOGWO、NSWOA、MOPSO、NSGA2)性能对比,包含6种评价指标,9个测试函数(提供MATLAB代码)
一、5种多目标优化算法简介 1.1MOAHA 1.2MOGWO 1.3NSWOA 1.4MOPSO 1.5NSGA2 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数(zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3)ÿ…...
用 LangChain 和 Milvus 从零搭建 LLM 应用
如何从零搭建一个 LLM 应用?不妨试试 LangChain Milvus 的组合拳。 作为开发 LLM 应用的框架,LangChain 内部不仅包含诸多模块,而且支持外部集成;Milvus 同样可以支持诸多 LLM 集成,二者结合除了可以轻松搭建一个 LL…...
[Bug解决] Invalid bound statement (not found)出现原因和解决方法
1、问题描述 在写了一个很普通的查询语句之后,出现了下面的报错信息 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.oauth.mapper.WxVisitorQrBeanMapper.selectByComIdAndEmpId at org.apache.ibatis.binding.Mappe…...
Qt:Qt3个窗口类的区别、VS与QT项目转换
一、Qt3个窗口类的区别 QMainWindow:包含菜单栏、工具栏、状态栏 QWidget:普通的一个窗口,什么也不包括 QDialog:对话框,常用来做登录窗口、弹出窗口(例如设置页面) QDialog实现简易登录界面…...
uni-app判断不同端
大家好,今天给大家分享的知识是在uni-app中如何区分是在什么端操作的程序 话不多说直接上代码: // #ifdef APP-PLUS<view>APP端</view>// #endif// #ifdef H5<view>H5端</view>// #endif// #ifdef MP<view>小程序端</v…...
计算机网络-网络设备防火墙是什么?
一、防火墙基本概念 前面我们学习了交换机、路由器是网络中常用的设备,现实中还有一个很重要的设备-防火墙。防火墙这一设备通常用于两个网络之间有针对性的、逻辑意义上的隔离。在网络通信领域,防火墙是一种安全设备。它用于保护一个网络区域免受来自另…...
MATLAB CGCS2000高斯投影坐标转经纬度坐标
坐标系转换这边需要用到mapping toolbox 首先根据原始(x,y)坐标对应的投影坐标系查询EPSG编号 例如这边CGCS2000 / 3-degree Gauss-Kruger CM 123E的编号就是4450 对应的编号可以https://blog.csdn.net/qq_41441896/article/details/104525296在这篇博…...
AI动态简报之技术前沿篇(2026.05.22)
📅 2026年5月22日 | 关注方向:AI技术突破 大模型创新 AI Agent 生成式AI 多模态AI 🔥 第1条:谷歌I/O 2026三箭齐发——Gemini 3.5 Flash速度碾压4倍、Spark全天候Agent、Omni全栈多模态 核心内容: 谷歌I/O 2026以…...
python拼装模型商城销售管理系统
目录同行可拿货,招校园代理 ,本人源头供货商项目概述核心功能技术栈特色亮点适用场景项目技术支持源码获取详细视频演示 :同行可合作点击我获取源码->获取博主联系方式->进我个人主页-->同行可拿货,招校园代理 ,本人源头供货商 项目概述 Python拼装模型商…...
DeepSeek-R1 vs Qwen2.5 vs Claude-3:17项硬指标对比,谁才是2024高性价比AI模型黑马?
更多请点击: https://kaifayun.com 第一章:DeepSeek性价比优势分析 DeepSeek 系列模型(如 DeepSeek-V2、DeepSeek-Coder、DeepSeek-MoE)在开源大模型生态中展现出显著的性价比优势,尤其在推理效率、训练成本与下游任务…...
【OpenClaw 进阶配置】如何让 MiniMax 搜索替代 SearXNG 作为 Web Search provider
【OpenClaw 进阶配置】如何让 MiniMax 搜索替代 SearXNG 作为 Web Search provider 标签: OpenClaw / MiniMax / 配置教程 / AI工具 踩坑记录 + 完整配置方案 前言 最近在配置 OpenClaw 的 web_search 工具,遇到了一个有意思的问题:明明已经在 tools.web.search.provider …...
医用超声图像干扰处理方法:原理、技术与实践
引言 超声成像作为一种无创、实时、无辐射的医学影像技术,在临床诊断中发挥着至关重要的作用。然而,超声图像在采集过程中极易受到各种物理和电子干扰,导致图像质量下降,影响医生的诊断准确性。常见的干扰包括斑点噪声、混响伪影、声影、镜面伪影以及由患者呼吸、运动引起…...
GEO学习能帮我提高AI搜索排名吗?
先直接回答这个问题:GEO不叫"排名",但效果比排名更直接。如果你理解了这句话,你就理解了GEO和SEO的本质区别。AI搜索没有"排名",只有"引用"传统SEO优化的目标是在搜索结果页面占据靠前位置——第几…...
从NPN到FET:一文看懂LDO内部调整管的演进史,以及如何根据你的项目(IoT、可穿戴、汽车电子)选择最优架构
从NPN到FET:LDO调整管技术演进与选型实战指南 在可穿戴设备的心率传感器突然断电的瞬间,工程师们才意识到选错LDO的代价——这恰恰揭示了调整管架构对系统可靠性的决定性影响。从早期笨重的NPN稳压器到如今纳米级MOSFET LDO,电源管理芯片的进…...
STM32G474RB用CMSIS-DAP下载程序,遇到一堆content mismatch错误?别急着换芯片,先检查这个硬件细节
STM32G474RB用CMSIS-DAP下载程序遇到content mismatch?可能是多设备干扰惹的祸 当你在实验室同时调试多块STM32开发板时,是否遇到过这样的场景:昨天还能正常烧录的STM32G474RB板卡,今天突然开始报出一连串content mismatch错误&am…...
Dism++终极指南:轻松掌握Windows系统优化与维护的10个关键技巧
Dism终极指南:轻松掌握Windows系统优化与维护的10个关键技巧 【免费下载链接】Dism-Multi-language Dism Multi-language Support & BUG Report 项目地址: https://gitcode.com/gh_mirrors/di/Dism-Multi-language 你是否曾经因为Windows系统变得越来越慢…...
