马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
Spring Boot + MyBatis-Plus 多租户
一、多租户架构概述
多租户(Multi-Tenancy)是 SaaS(软件即服务)模式的核心技术,旨在通过单一应用实例为多个租户提供服务,同时包管数据隔离。其实现方式重要分为三种:
- 独立数据库:每个租户拥有独立数据库,隔离性最强但成本高。
- 共享数据库独立 Schema:共享数据库实例但逻辑分离(如 PostgreSQL 的 Schema),平衡安全性与成本。
- 共享数据库共享表:通过 tenant_id 字段区分数据,成本最低但需依赖应用层过滤。
二、字段隔离模式(共享表)
1. 核心原理
在每张表中添加 tenant_id 字段,通过 MyBatis-Plus 的 TenantLineInnerInterceptor 插件自动注入租户条件。所有 SQL 操作自动附加 tenant_id = ? 过滤条件,实现数据隔离。
2. 实现步骤
(1) 添加依赖
- <dependency>
- <groupId>com.baomidou</groupId>
- <artifactId>mybatis-plus-boot-starter</artifactId>
- <version>3.5.3.1</version>
- </dependency>
复制代码 (2) 定义租户上下文(注意使用TransmittableThreadLocal)
注意使用 @Async 执行异步任务时,由于异步任务运行在新线程或线程池线程中,ThreadLocal 变量的值无法自动通报到子线程,导致获取到的值为 null。阿里巴巴开源的 TransmittableThreadLocal 支持线程池场景下的上下文通报。
(2.1) 整合TransmittableThreadLocal,实现异步通报
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>transmittable-thread-local</artifactId>
- <version>2.14.2</version>
- </dependency>
复制代码
- TTL兼容的异步线程池,通过 ThreadPoolTaskExecutor 结合 Executors 装饰线程池:
- @Configuration
- @EnableAsync
- public class AsyncConfig implements AsyncConfigurer {
- @Override
- public Executor getAsyncExecutor() {
- ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
- executor.setCorePoolSize(4);
- executor.setMaxPoolSize(8);
- executor.setQueueCapacity(100);
- executor.setThreadNamePrefix("ttl-async-");
- executor.initialize();
- // 使用 TTL 装饰线程池
- return TtlExecutors.getTtlExecutor(executor.getThreadPoolExecutor());
- }
- }
复制代码 (2.2) 定义租户上下文
- public class TenantContext {
- private static final TransmittableThreadLocal<String> CURRENT_TENANT= new TransmittableThreadLocal();
- public static void setTenantId(String tenantId) {
- CURRENT_TENANT.set(tenantId);
- }
- public static String getTenantId() {
- return CURRENT_TENANT.get();
- }
- public static void clear() {
- CURRENT_TENANT.remove();
- }
- }
复制代码 (3) 配置拦截器获取租户 ID
- @Configuration
- public class WebConfig implements WebMvcConfigurer {
- @Override
- public void addInterceptors(InterceptorRegistry registry) {
- registry.addInterceptor(new HandlerInterceptor() {
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- String tenantId = request.getHeader("X-Tenant-ID");
- TenantContext.setTenantId(tenantId);
- return true;
- }
- });
- }
- }
复制代码 (4) 配置 MyBatis-Plus 插件
- @Configuration
- public class MyBatisPlusConfig {
- @Bean
- public MybatisPlusInterceptor mybatisPlusInterceptor() {
- MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
- interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
- @Override
- public Expression getTenantId() {
- return new StringValue(TenantContext.getTenantId());
- }
- @Override
- public String getTenantIdColumn() {
- return "tenant_id"; // 数据库字段名
- }
- @Override
- public boolean ignoreTable(String tableName) {
- return Arrays.asList("sys_config").contains(tableName); // 忽略系统表
- }
- }));
- return interceptor;
- }
- }
复制代码 (5) 实体类标志租户字段
- @Data
- public class User {
- private Long id;
- private String name;
- @TableField(value = "tenant_id", fill = FieldFill.INSERT)
- private String tenantId; // 自动填充租户ID
- }
复制代码 (6) 测试接口
- @RestController
- public class UserController {
- @Autowired
- private UserMapper userMapper;
- @GetMapping("/users")
- public List<User> listUsers() {
- return userMapper.selectList(new QueryWrapper<>());
- }
- }
复制代码 结果:查询 SELECT * FROM user WHERE tenant_id = 'tenant1',插入时自动填充 tenant_id。
三、高级配置与优化
1. 混淆模式支持
结合字段隔离和动态数据源,比方:
- 主业务表使用独立数据库(动态数据源)
- 日记表使用共享表(字段隔离)
2. 忽略租户过滤
通过 @InterceptorIgnore(tenantLine = "true") 注解跳过特定方法:
- @InterceptorIgnore(tenantLine = "true")
- public List<User> selectAll() {
- return userMapper.selectList(null);
- }
复制代码 3. 多租户数据源自动加载
从数据库加载租户数据源配置:
- @Bean
- public DataSource initialDataSource() {
- // 查询租户表获取数据源配置
- List<Tenant> tenants = tenantMapper.selectList(null);
- Map<Object, Object> dataSources = tenants.stream()
- .collect(Collectors.toMap(Tenant::getId, tenant -> createDataSource(tenant)));
- dynamicDataSource.setTargetDataSources(dataSources);
- }
复制代码 4. 性能优化
- 毗连池管理:使用 Druid 或 HikariCP 配置毗连池,避免资源泄漏。
- 缓存机制:缓存租户数据源配置,减少数据库查询频率。
五、常见问题与解决方案
问题解决方案多表关联查询未注入租户条件升级 MyBatis-Plus 至 3.5.0+,修复关联查询的租户条件注入问题插入时 tenant_id 重复查抄实体类 tenant_id 字段的自动填充计谋,避免手动赋值动态数据源切换失败确保 DynamicDataSourceContextHolder 在异步线程中通报租户ID未通报导致空指针在拦截器中添加租户ID校验,返回明确错误提示
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |