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

Spring Boot + MyBatis-Plus 多租户场景下:如何实现“一次设置,全程忽略租户”?支持同步/异步/嵌套调用!

在企业级应用中,多租户架构(Multi-Tenancy)是一个非常常见的需求。比如 SaaS 平台中,每个客户(租户)的数据需要隔离存储,通常通过在每张业务表中增加一个tenant_id字段来实现。

MyBatis-Plus 提供了强大的多租户插件(TenantLineInnerInterceptor),可以自动在 SQL 中注入租户条件。但在某些特殊场景下,我们可能希望临时忽略租户过滤,比如:

  • 管理员查看所有租户数据
  • 数据迁移、批量处理任务
  • 异步任务中绕过多租户限制

这时候问题来了:如何做到“一次设置,全程忽略租户”?即使在异步线程、嵌套方法调用中也能生效?

今天我们就来手把手教你实现这个高级功能!


一、需求场景说明

假设你正在开发一个 SaaS 后台管理系统,普通用户只能看到自己租户的数据,但超级管理员可以查看全部租户的数据。

✅ 正确做法:在请求入口处判断是否为超级管理员,如果是,则在整个请求链路(包括异步任务、Service 嵌套调用)中都忽略租户过滤。

❌ 反例做法:每次调用 Mapper 时手动传参或写特殊 SQL,代码冗余且容易出错。


二、技术选型与原理

  • Spring Boot 3.x
  • MyBatis-Plus 3.5+
  • ThreadLocal + InheritableThreadLocal:用于在线程上下文中传递“忽略租户”标志
  • 自定义 MyBatis-Plus 租户处理器:动态决定是否应用租户过滤

💡 核心思想:
使用InheritableThreadLocal存储“是否忽略租户”的开关。
因为普通ThreadLocal在异步线程(如@Async)中无法继承父线程的值,而InheritableThreadLocal可以。


三、代码实现

1. 自定义忽略租户上下文工具类

// TenantIgnoreContext.java public class TenantIgnoreContext { // 使用 InheritableThreadLocal 支持子线程继承 private static final InheritableThreadLocal<Boolean> IGNORE_TENANT = new InheritableThreadLocal<>(); public static void setIgnore(boolean ignore) { IGNORE_TENANT.set(ignore); } public static boolean isIgnore() { Boolean ignore = IGNORE_TENANT.get(); return ignore != null && ignore; } public static void clear() { IGNORE_TENANT.remove(); } }

⚠️ 注意:必须在请求结束时调用clear(),否则可能造成内存泄漏或污染下一个请求!


2. 自定义租户处理器(关键!)

// CustomTenantHandler.java import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; public class CustomTenantHandler implements TenantLineHandler { @Override public Expression getTenantId() { // 正常返回租户ID,比如从登录信息中获取 // 这里简化为固定值,实际项目应从 SecurityContext 或 ThreadLocal 获取 return new LongValue(1L); } @Override public String getTenantIdColumn() { return "tenant_id"; } @Override public boolean ignoreTable(String tableName) { // 某些表不需要租户隔离,比如字典表、系统配置表 return false; } // 重写此方法!决定是否应用租户过滤 @Override public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) { return TenantIgnoreContext.isIgnore(); } // 关键:全局控制是否忽略租户 @Override public boolean ignoreTableWhenInsertOrUpdateOrDelete(String tableName) { return TenantIgnoreContext.isIgnore(); } // MyBatis-Plus 3.5+ 推荐重写此方法 @Override public boolean ignoreTableForSelect(String tableName) { return TenantIgnoreContext.isIgnore(); } }

🔥 重点:所有ignoreXXX方法都返回TenantIgnoreContext.isIgnore(),实现统一控制。


3. 配置 MyBatis-Plus 多租户插件

// MybatisPlusConfig.java @Configuration @MapperScan("com.example.demo.mapper") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 多租户插件 TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor(); tenantInterceptor.setTenantLineHandler(new CustomTenantHandler()); interceptor.addInnerInterceptor(tenantInterceptor); // 分页插件等其他拦截器... interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

4. 在 Controller 中使用(支持同步/异步)

@RestController public class UserController { @Autowired private UserService userService; @Autowired private AsyncTask asyncTask; @GetMapping("/users") public List<User> getAllUsers(@RequestParam(required = false) Boolean admin) { try { // 如果是管理员,开启忽略租户模式 if (Boolean.TRUE.equals(admin)) { TenantIgnoreContext.setIgnore(true); } // 同步调用 List<User> users = userService.listAll(); // 异步调用(子线程会继承 ignore 标志!) asyncTask.logUserCount(); return users; } finally { // 必须清理!防止 ThreadLocal 泄漏 TenantIgnoreContext.clear(); } } }
// UserService.java @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> listAll() { // 即使嵌套调用,也能正确识别是否忽略租户 return userMapper.selectList(null); } }
// AsyncTask.java @Component public class AsyncTask { @Autowired private UserMapper userMapper; @Async public void logUserCount() { // 异步线程中依然能拿到 ignore 标志! long count = userMapper.selectCount(null); System.out.println("Total users (ignoring tenant): " + count); } }

✅ 测试结果:

  • 访问/users?admin=true→ 查询所有租户数据
  • 访问/users→ 仅查询当前租户数据
  • 异步任务中也生效!

四、反例 & 注意事项

❌ 反例1:只在 Service 层硬编码忽略

// 错误!每次都要改,无法复用 public List<User> getAllUsersForAdmin() { // 手动写 SQL 或关闭插件?不可维护! }

❌ 反例2:忘记清理 ThreadLocal

// 危险!可能导致下一个请求错误地继承了 ignore 标志 TenantIgnoreContext.setIgnore(true); // ... 忘记 clear()

⚠️ 注意事项:

  1. 必须使用InheritableThreadLocal,否则@Async、线程池中的任务无法继承上下文。
  2. 务必在 finally 块中调用clear(),建议封装成 AOP 切面自动清理。
  3. 不要在过滤器/拦截器之外的地方随意设置,避免逻辑混乱。
  4. 测试时要覆盖同步、异步、异常路径,确保上下文正确传播和清理。

五、进阶建议:用 AOP 自动管理上下文

你可以写一个注解@IgnoreTenant,配合 AOP 自动设置和清理:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface IgnoreTenant {}
@Aspect @Component public class TenantIgnoreAspect { @Around("@annotation(IgnoreTenant)") public Object ignoreTenant(ProceedingJoinPoint joinPoint) throws Throwable { try { TenantIgnoreContext.setIgnore(true); return joinPoint.proceed(); } finally { TenantIgnoreContext.clear(); } } }

然后在方法上直接使用:

@IgnoreTenant @GetMapping("/admin/users") public List<User> adminGetAllUsers() { return userService.listAll(); // 自动忽略租户 }

总结

通过InheritableThreadLocal + 自定义 TenantHandler,我们实现了:

✅ 一次设置,全程生效
✅ 支持同步、异步、嵌套调用
✅ 代码清晰,无侵入性
✅ 安全可控,避免内存泄漏

这才是多租户系统中“临时绕过租户隔离”的优雅解法!


视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

http://www.jsqmd.com/news/143652/

相关文章:

  • 2025液压中心架行业口碑TOP5权威测评:沃伦贝格中心架性价比如何 - 工业推荐榜
  • Windows系统文件wpnapps.dll丢失损坏 下载方法
  • SpringBoot+Vue 金帝豪斯健身房管理系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】
  • 带进度条复制文件夹
  • PaddlePaddle元宇宙场景AI生成模型
  • 2025年南京地区有实力的短视频拍摄专业公司推荐:看哪家口碑不错? - 工业品牌热点
  • 2025年国内评价好的格宾石笼网实力厂家选哪家,柔韧抗压石笼网/双隔板石笼网/六角石笼网/锌铝合金石笼网格宾石笼网供应商找哪家 - 品牌推荐师
  • PaddlePaddle职业教育课程推荐模型
  • Tang-Nano-1K移植vio_uart
  • 2025年中海关数据专业公司推荐:资质齐全的海关数据公司有哪些? - 工业品网
  • 智普AI Open-AutoGLM开源:从零部署到实战调优的7个关键步骤
  • 2026年GEO合作伙伴优选指南:GEO公司推荐,精准匹配策略与推荐清单 - 资讯焦点
  • PaddlePaddle个性化学习路径推荐系统
  • 广州1688网店代运营找哪家?加工定制/金属加工1688代运营托管效果好的公司推荐 - mypinpai
  • PaddlePaddle数据库查询优化AI辅助
  • 如何实现 RustFS 对象存储的前端直传?
  • 为下一代超算设计CPU:HBM带来的性能革新
  • PaddlePaddle产品需求挖掘NLP模型应用
  • Open-AutoGLM控制手机的底层原理曝光:开发者绝不会告诉你的秘密
  • zz一些GPU的知识
  • Open-AutoGLM Win版安装踩坑全记录(90%用户忽略的3个关键步骤)
  • CDN07游戏盾SDK方案详解:为游戏而生的防攻击与稳定连接方案
  • Open-AutoGLM架构实战:3步实现模型自动优化与部署集成
  • 代码样式测试
  • PaddlePaddle面试问题自动生成系统
  • 2025年大型律所国际仲裁律师排行榜,新测评精选国际仲裁资深律师推荐 - myqiye
  • 2025年智能锁电池制造商哪家好排行榜,智能锁电池生产商哪家好 - 工业品网
  • Open-AutoGLM Win版实测对比:到底比Linux版慢还是快?数据说话
  • 【大模型私有化部署新突破】:Open-AutoGLM一键部署方案全公开
  • 别再盲目调参了!Open-AutoGLM 2.0 智能优化流程详解(仅限专业人士)