如何添加“默认给Sql查询语句加上租户条件”的功能

从零实现"默认给 SQL 查询语句加上租户条件 "的功能,本质上是利用 MyBatis Plus 的插件机制配合 ThreadLocal 上下文来实现的。

我们需要构建一条完整的 "数据 -> 规则 -> 执行" 的链路。以下是标准化的 5 步实现指南


第一步:准备"背包" (定义上下文容器)

你需要一个地方在当前线程中存储"当前租户是谁"。

代码核心: 利用 ThreadLocal

java 复制代码
public class TenantContextHolder {
    // 存放当前租户ID
    private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();

    public static void setTenantId(Long tenantId) {
        TENANT_ID.set(tenantId);
    }

    public static Long getTenantId() {
        return TENANT_ID.get();
    }

    public static void clear() {
        TENANT_ID.remove();
    }
}

第二步:制定"规则" (实现 Handler 接口)

你需要告诉 MyBatis Plus 具体的过滤逻辑:租户ID是多少?列名叫什么?哪些表不需要加?

代码核心: 实现 TenantLineHandler 接口。

java 复制代码
@Component
public class MyTenantLineHandler implements TenantLineHandler {

    // 1. 告诉 MP,当前租户ID是多少 (从背包里拿)
    @Override
    public Expression getTenantId() {
        Long tenantId = TenantContextHolder.getTenantId();
        // 如果没拿到ID(比如没登录),返回 NullValue 可能会导致报错或查不到数据
        // 通常这里会做判空或者返回默认值
        return new LongValue(tenantId);
    }

    // 2. 告诉 MP,数据库里租户列的名字叫什么
    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    // 3. 告诉 MP,哪些表需要忽略 (白名单)
    @Override
    public boolean ignoreTable(String tableName) {
        // A. 全局白名单:系统表(字典、菜单)不需要隔离
        if ("sys_dict".equals(tableName) || "sys_menu".equals(tableName)) {
            return true;
        }
        // B. 动态白名单:配合 @TenantIgnore 注解使用
        if (TenantContextHolder.isIgnore()) {
            return true;
        }
        // 默认:必须加过滤条件
        return false;
    }
}

第三步:组装"引擎" (配置 MyBatis 拦截器)

有了规则(Handler),你需要把它交给执行者(Interceptor),并把执行者放入 MyBatis 的插件链中。

代码核心: 配置 MybatisPlusInterceptor Bean。

java 复制代码
@Configuration
public class MybatisConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(MyTenantLineHandler tenantLineHandler) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 核心动作:创建租户拦截器,并注入上面的规则 Handler
        // ⚠️注意:建议放在分页插件之前
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));

        // 添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        return interceptor;
    }
}

第四步:数据"注入" (配置 Web 过滤器)

引擎装好了,但还得有人把燃料(租户ID)塞进第一步的"背包"里。通常是在请求刚进来时处理。

代码核心: 实现 FilterHandlerInterceptor

java 复制代码
@Component
public class TenantContextFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        try {
            // 1. 从请求头 Header 获取 tenant-id
            HttpServletRequest req = (HttpServletRequest) request;
            String tenantIdStr = req.getHeader("tenant-id");
            
            if (tenantIdStr != null) {
                // 2. 塞进 ThreadLocal 背包
                TenantContextHolder.setTenantId(Long.valueOf(tenantIdStr));
            }
            
            // 3. 放行,执行后续业务逻辑 (Service -> Mapper -> SQL拦截器)
            chain.doFilter(request, response);
            
        } finally {
            // 4. 【重要】请求结束,清空背包,防止线程污染
            TenantContextHolder.clear();
        }
    }
}

🌟 总结:这一套下来发生了什么?

  1. 请求进来 :Filter 从 Header 拿到 tenant_id=1,存入 TenantContextHolder
  2. 业务查询 :你写了 userMapper.selectList(null)
  3. 拦截改写TenantLineInnerInterceptor 拦截 SQL,调用 MyTenantLineHandler
  4. 读取规则 :Handler 从 TenantContextHolder 拿到 1
  5. SQL 变身 :SQL 被自动拼接为 SELECT * FROM user WHERE tenant_id = 1
  6. 请求结束 :Filter 清空 TenantContextHolder

这就是实现"全自动多租户隔离"的完整标准流程。

相关推荐
pengdott5 分钟前
Oracle RAC内存融合技术深度解析:集群性能的幕后引擎
数据库·oracle
码云数智-大飞28 分钟前
使用 Python 高效提取 PDF 中的表格数据并导出为 TXT 或 Excel
python
csudata43 分钟前
绿色便携版PostgreSQL发行版重磅发布
数据库·postgresql
阳光九叶草LXGZXJ1 小时前
达梦数据库-学习-48-DmDrs控制台命令(同步之Manager、CPT模块)
linux·运维·数据库·sql·学习
我科绝伦(Huanhuan Zhou)2 小时前
脚本再升级,兼容Oracle 26ai一键安装
数据库·oracle
biuyyyxxx2 小时前
Python自动化办公学习笔记(一) 工具安装&教程
笔记·python·学习·自动化
极客数模2 小时前
【2026美赛赛题初步翻译F题】2026_ICM_Problem_F
大数据·c语言·python·数学建模·matlab
野生绿箭侠2 小时前
Ncos 2.3.2 版本集成达梦数据库
数据库
仍然.3 小时前
MYSQL--约束
数据库·mysql
乡野码圣3 小时前
【RK3588 Android12】RCU机制
java·jvm·数据库