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

MyBatis sql拦截器实现一个自动根据租户进行分表的方案

需求描述:

在一个多租户系统中,通过 MyBatis 实现动态数据表分离。具体来说,您希望通过 MyBatis 拦截器在执行 SQL 时自动将表名根据当前租户 ID (tenantId) 进行修改。这样,每个租户的数据就可以存储在专属于它们的表中,实现数据隔离。

需求细节:

  1. 拦截 SQL 语句:使用 MyBatis 的 @Intercepts 注解和 Interceptor 接口来拦截 SQL 语句。

  2. 修改表名:在 SQL 执行时,根据提供的租户 ID (tenantId) 动态地修改 SQL 语句中的表名。例如,如果原表名为 ALARM_RECORD_INFO,租户 ID 为 1001,则修改后的表名应为 ALARM_RECORD_INFO_1001

  3. 获取租户 ID:从 MyBatis Mapper 接口方法的参数中获取 tenantId。这是通过在 Mapper 接口的方法参数上使用 @Param("tenantId") 注解实现的。

  4. 配置:在 mybatis-config.xml 文件中配置拦截器,并指定需要根据租户 ID 分表的表名。

  5. 映射器文件 (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拦截器实现一个自动根据租户进行分表的方案

需求描述&#xff1a; 在一个多租户系统中&#xff0c;通过 MyBatis 实现动态数据表分离。具体来说&#xff0c;您希望通过 MyBatis 拦截器在执行 SQL 时自动将表名根据当前租户 ID (tenantId) 进行修改。这样&#xff0c;每个租户的数据就可以存储在专属于它们的表中&#xf…...

TiDB in 2023, 一次简单的回顾丨PingCAP 唐刘

2023 年已经过去&#xff0c;TiDB 经过了一年的迭代&#xff0c;又往前进步了一点点&#xff0c;我们非常自豪的看到&#xff0c;TiDB 正在不断地帮助我们的客户成功&#xff0c;包括但不限于&#xff1a; ○ 首个云原生、分布式、全栈国产化银行核心业务系统投产上线丨TiDB …...

debug - 只要在内存中有显示相关的数据, 就会被CE找到

文章目录 debug - 只要在内存中有显示相关的实际数据, 就会被CE找到概述笔记demo实现demo运行效果用CE查找实际数据地址找到自己的调试点 - 方法1找到自己的调试点 - 方法2打补丁备注END debug - 只要在内存中有显示相关的实际数据, 就会被CE找到 概述 自己写了一个demo, 想验…...

Redis 单个与多节点如何实现分布式锁

分布式锁 在许多环境中&#xff0c;分布式锁是非常有用的原语&#xff0c;在这些环境中&#xff0c;不同的进程必须以互斥的方式操作共享资源。在应对并发问题时&#xff0c;Redis 客户端还可以通过加锁的方式&#xff0c;来控制并发写操作对共享数据的修改&#xff0c;从而保…...

频段划分学习射频知识的意义

一、射频电路设计与低频电路设计的不同点 随着频率提高&#xff0c;相应电磁波的波长与变得可与分立电路元件的尺寸相比拟时&#xff0c;电阻、电容和电感这些元件的电响应&#xff0c;将偏离他们的理想频率特性。以 WIFI 2.4G 频段为例&#xff0c;当频率为 2437MHz&#xff0…...

Effective Objective-C 学习(四)

掌握GCD及操作队列的使用时机 在执行后台任务时&#xff0c;GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue&#xff0c;它虽然与 GCD 不同&#xff0c;但是却与之相关&#xff0c;开发者可以把操作以 NSOperation 子类的形式放在队列中&#xff0c;而这些操作也…...

欢迎来到IT时代----盘点曾经爆火全网的计算机电影

计算机专业必看的几部电影 计算机专业必看的几部电影&#xff0c;就像一场精彩的编程盛宴&#xff01;《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力&#xff1b;《社交网络》揭示了互联网巨头的创业之路&#xff0c;《源代码》带你穿越时间解救世界&#xff0c;这…...

光芒绽放:妙用“GLAD原则”打造标准的数据可视化图表

光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表 文章目录 光芒绽放&#xff1a;妙用“GLAD原则”打造标准的数据可视化图表前言一、可视化工具有哪些&#xff1f;二、那如何做出正确可视化图表 &#xff1f;GLAD原则1.G原则2.L原则3.A原则4.D原则 三、总结最后…...

如何设计出用于喜欢的界面

要设计出用户喜欢的界面&#xff0c;你可以考虑以下几个方面&#xff1a; 用户研究&#xff1a;首先要了解用户的需求和偏好。你可以通过用户调研、用户访谈和数据分析来获取这些信息。了解用户的行为模式、喜好和痛点&#xff0c;有助于设计出更吸引人的界面。 直观的布局&am…...

第三篇【传奇开心果系列】Python的文本和语音相互转换库技术点案例示例:pyttsx3实现语音助手经典案例

传奇开心果短博文系列 系列短博文目录Python的文本和语音相互转换库技术点案例示例系列 短博文目录一、项目背景和目标二、雏形示例代码三、扩展思路介绍四、与其他库和API集成示例代码五、自定义语音示例代码六、多语言支持示例代码七、语音控制应用程序示例代码八、文本转语音…...

JS中数组的常用方法

concat() 连接两个或更多的数组&#xff0c;并返回结果。 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() 把数组的所有元素放入一个字符串。元素通过指定…...

最好用的论文检索网站

网站展示&#xff1a; 网站链接 sci-hub文献检索 用途&#xff1a; 可以用文章的DOI来检索并下载文章...

AI专题:AI巨轮滚滚向前

今天分享的是电子系列深度研究报告&#xff1a;《AI专题&#xff1a;AI巨轮滚滚向前》。 &#xff08;报告出品方&#xff1a;方正证券&#xff09; 报告共计&#xff1a;65页 来源&#xff1a;人工智能学派 Gemini 1.5 Pro 性能显著增强&#xff0c;长上下文理解取得突破 …...

SpringBoot常见问题

1 引言 Spring Boot是一个基于Spring框架的快速开发脚手架&#xff0c;它简化了Spring应用的初始化和搭建过程&#xff0c;提供了众多便利的功能和特性&#xff0c;比如自动配置、嵌入式Tomcat等&#xff0c;让开发人员可以更加专注于业务逻辑的实现。   Spring Boot还提供了…...

五种多目标优化算法(MOAHA、MOGWO、NSWOA、MOPSO、NSGA2)性能对比,包含6种评价指标,9个测试函数(提供MATLAB代码)

一、5种多目标优化算法简介 1.1MOAHA 1.2MOGWO 1.3NSWOA 1.4MOPSO 1.5NSGA2 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数&#xff08;zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3&#xff09;&#xff…...

用 LangChain 和 Milvus 从零搭建 LLM 应用

如何从零搭建一个 LLM 应用&#xff1f;不妨试试 LangChain Milvus 的组合拳。 作为开发 LLM 应用的框架&#xff0c;LangChain 内部不仅包含诸多模块&#xff0c;而且支持外部集成&#xff1b;Milvus 同样可以支持诸多 LLM 集成&#xff0c;二者结合除了可以轻松搭建一个 LL…...

[Bug解决] Invalid bound statement (not found)出现原因和解决方法

1、问题描述 在写了一个很普通的查询语句之后&#xff0c;出现了下面的报错信息 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&#xff1a;包含菜单栏、工具栏、状态栏 QWidget&#xff1a;普通的一个窗口&#xff0c;什么也不包括 QDialog&#xff1a;对话框&#xff0c;常用来做登录窗口、弹出窗口&#xff08;例如设置页面&#xff09; QDialog实现简易登录界面…...

uni-app判断不同端

大家好&#xff0c;今天给大家分享的知识是在uni-app中如何区分是在什么端操作的程序 话不多说直接上代码&#xff1a; // #ifdef APP-PLUS<view>APP端</view>// #endif// #ifdef H5<view>H5端</view>// #endif// #ifdef MP<view>小程序端</v…...

计算机网络-网络设备防火墙是什么?

一、防火墙基本概念 前面我们学习了交换机、路由器是网络中常用的设备&#xff0c;现实中还有一个很重要的设备-防火墙。防火墙这一设备通常用于两个网络之间有针对性的、逻辑意义上的隔离。在网络通信领域&#xff0c;防火墙是一种安全设备。它用于保护一个网络区域免受来自另…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

spring Security对RBAC及其ABAC的支持使用

RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型&#xff0c;它将权限分配给角色&#xff0c;再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...

Python第七周作业

Python第七周作业 文章目录 Python第七周作业 1.使用open以只读模式打开文件data.txt&#xff0c;并逐行打印内容 2.使用pathlib模块获取当前脚本的绝对路径&#xff0c;并创建logs目录&#xff08;若不存在&#xff09; 3.递归遍历目录data&#xff0c;输出所有.csv文件的路径…...

Selenium 查找页面元素的方式

Selenium 查找页面元素的方式 Selenium 提供了多种方法来查找网页中的元素&#xff0c;以下是主要的定位方式&#xff1a; 基本定位方式 通过ID定位 driver.find_element(By.ID, "element_id")通过Name定位 driver.find_element(By.NAME, "element_name"…...

记一次spark在docker本地启动报错

1&#xff0c;背景 在docker中部署spark服务和调用spark服务的微服务&#xff0c;微服务之间通过fegin调用 2&#xff0c;问题&#xff0c;docker容器中服务器来后&#xff0c;注册中心都有&#xff0c;调用服务也正常&#xff0c;但是调用spark启动任务后报错&#xff0c;报错…...

claude3.7高阶玩法,生成系统架构图,国内直接使用

文章目录 零、前言一、操作指南操作指导 二、提示词模板三、实战图书管理系统通过4o模型生成系统描述通过claude3.7生成系统架构图svg代码转换成图片 在线考试系统通过4o模型生成系统描述通过claude3.7生成系统架构图svg代码转换成图片 四、感受 零、前言 现在很多AI大模型可以…...