如何添加“默认给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)塞进第一步的"背包"里。通常是在请求刚进来时处理。

代码核心: 实现 Filter 或 HandlerInterceptor。

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。

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

相关推荐
昵称画16 小时前
交接班记录电子化:三班之间的信息不漏
开发语言·数据库·人工智能·低代码·php
“AI国潮设计-小江”16 小时前
【AIGC实战】Python+SDXL批量生成“潮汕英歌舞不倒翁”IP | 角色一致性与多场景融合方案,附完整代码
开发语言·人工智能·python·prompt·aigc
Linux_cy16 小时前
服务器管理工具哪个好用?我用了七八个
运维·服务器·javascript·python·ssh·面板·gmssh
MoRanzhi120316 小时前
第6篇(下)· 使用 PyQt5 实现 2048 高性能计算版:统一运行控制与跨模式安全交接
python·pyqt·进程管理·状态机·软件架构·并发控制·数据一致性
code tsunami16 小时前
【无标题】
开发语言·python
缓慢更新17 小时前
审批的加签与会签:多人意见怎么合
android·java·数据库·低代码·rxjava
CodeGL17 小时前
10、Python基础之进程和线程
开发语言·python
跨境联盟17 小时前
大健康实体如何搭建产品差异化:对比普通营养品与合规跨境膳食补充剂服务模式
数据库·ai·健康医疗·健康管理·精准营养
有想法的py工程师17 小时前
PostgreSQL EXPLAIN中的Cost模型分析(random_page_cost参数):优化器眼中的代价世界与执行计划生成
数据库·postgresql
前进的李工18 小时前
飞书企业知识库设计+后端高频八股
数据库·redis·后端·mysql·性能优化·飞书·rag