Skip to content

多租户模块(MyBatis Plus实现)

最后更新: 11 分钟前
实践版本: v2.13.0

简介

continew-starter-extension-tenant-mp 是 ContiNew Starter 提供的基于 MyBatis Plus 实现的多租户扩展模块,支持行级和数据源级两种租户隔离策略,提供开箱即用的 SaaS 多租户解决方案。通过简单的配置和注解,可快速实现租户数据隔离,保障不同租户数据的安全性和独立性。

主要特性

  • 双重隔离策略: 支持行级隔离和数据源级隔离两种方式,满足不同场景需求
  • 透明化处理: 基于 MyBatis Plus 拦截器,对业务代码无侵入
  • 动态数据源: 集成 Dynamic Datasource,支持租户级数据源动态切换
  • 上下文管理: 基于 ThreadLocal 的租户上下文管理,支持线程间传递
  • 灵活配置: 支持忽略特定表和超级租户等高级配置
  • 注解支持: 提供 @TenantIgnore 注解,方便排除特定方法或类

隔离策略

行级隔离(LINE)

在 SQL 查询时自动添加租户 ID 条件,所有租户数据存储在同一个数据库中,通过租户 ID 字段进行数据隔离。

优点:

  • 资源利用率高,维护成本低
  • 数据迁移和备份简单
  • 适合租户数量多但数据量适中的场景

缺点:

  • 数据安全性相对较低
  • 单表数据量大时查询性能可能受影响

数据源级隔离(DATASOURCE)

每个租户使用独立的数据库,通过动态切换数据源实现隔离。

优点:

  • 数据安全性高,租户间完全隔离
  • 单租户性能优异
  • 支持租户定制化需求

缺点:

  • 资源消耗大,维护成本高
  • 数据迁移和跨租户查询复杂

使用步骤

引入依赖

pom.xml 中添加以下依赖:

xml
<dependency>
    <groupId>top.continew.starter</groupId>
    <artifactId>continew-starter-extension-tenant-mp</artifactId>
</dependency>

全局配置

配置详情请查看:top.continew.starter.extension.tenant.autoconfigure.TenantProperties

yaml
--- ### 多租户配置
continew-starter.tenant:
  enabled: true
  # 租户隔离级别:LINE(行级)、DATASOURCE(数据源级)
  isolation-level: LINE
  # 租户 ID 列名(默认:tenant_id)
  tenant-id-column: tenant_id
  # 请求头中租户 ID 键名(默认:X-Tenant-Id)
  tenant-id-header: X-Tenant-Id
  # 超级租户 ID(默认:1)
  super-tenant-id: 1
  # 多租户忽略的表
  ignore-tables:
    - gen_config # 代码生成
    - gen_field_config
    - sys_dict # 字典表
    - sys_dict_item
    - sys_option #参数
    - sys_storage # 存储配置
    - sys_tenant # 租户
    - sys_tenant_package
    - sys_tenant_db_connect
    - sys_app #应用
    - sys_client #客户端管理
    - sys_sms_config
    - sys_sms_log

实现 TenantProvider

创建租户提供者实现类,用于根据租户 ID 获取租户上下文信息:

java
package top.continew.sample.config;

import org.springframework.stereotype.Component;
import top.continew.starter.extension.tenant.config.TenantDataSource;
import top.continew.starter.extension.tenant.config.TenantProvider;
import top.continew.starter.extension.tenant.context.TenantContext;
import top.continew.starter.extension.tenant.enums.TenantIsolationLevel;

/**
 * 租户提供者实现类
 *
 * @author Charles7c
 */
@Component
public class DefaultTenantProvider implements TenantProvider {

    @Override
    public TenantContext getByTenantId(String tenantId, boolean isVerify) {
        // 根据租户 ID 查询租户信息(这里仅作示例)
        TenantContext context = new TenantContext();
        context.setTenantId(Long.valueOf(tenantId));
        
        // 行级隔离示例
        context.setIsolationLevel(TenantIsolationLevel.LINE);
        
        // 数据源级隔离示例(需要配置租户专属数据源)
        if ("2".equals(tenantId)) {
            context.setIsolationLevel(TenantIsolationLevel.DATASOURCE);
            TenantDataSource dataSource = new TenantDataSource();
            dataSource.setUrl("jdbc:mysql://localhost:3306/tenant_2");
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            context.setDataSource(dataSource);
        }
        
        return context;
    }
}

数据库表设计

行级隔离表设计

对于行级隔离,需要在相关表中添加租户 ID 字段:

sql
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(64) NOT NULL COMMENT '用户名',
  `email` varchar(128) DEFAULT NULL COMMENT '邮箱',
  `tenant_id` bigint NOT NULL COMMENT '租户ID',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB COMMENT='用户表';

数据源级隔离

数据源级隔离时,每个租户使用独立的数据库,表结构不需要租户 ID 字段。

Web 层集成

多租户模块会自动从请求头中获取租户 ID,并设置到租户上下文中。默认的请求头键名为 X-Tenant-Id,可通过配置修改。

javascript
// 前端请求示例
fetch('/api/users', {
  headers: {
    'X-Tenant-Id': '1001',
    'Content-Type': 'application/json'
  }
})

使用注解

@TenantIgnore

用于忽略多租户处理的方法或类:

java
@RestController
@RequestMapping("/system")
public class SystemController {
    
    /**
     * 获取系统配置(忽略租户隔离)
     */
    @TenantIgnore
    @GetMapping("/config")
    public R<SystemConfig> getSystemConfig() {
        // 该方法不会添加租户条件
        return R.ok(systemService.getConfig());
    }
}
java
/**
 * 系统配置 Service(整个类忽略租户隔离)
 */
@TenantIgnore
@Service
public class SystemConfigService {
    
    public SystemConfig getConfig() {
        // 该类的所有方法都不会添加租户条件
        return configMapper.selectOne(null);
    }
}

编程式调用

可以通过 TenantHandler 在代码中手动切换租户上下文:

java
@Service
public class UserService {
    
    @Autowired
    private TenantHandler tenantHandler;
    
    @Autowired
    private UserMapper userMapper;
    
    /**
     * 跨租户查询用户数据
     */
    public void crossTenantQuery(Long targetTenantId) {
        // 在指定租户上下文中执行操作
        tenantHandler.execute(targetTenantId, () -> {
            List<User> users = userMapper.selectList(null);
            // 处理用户数据
        });
    }
}

核心组件

TenantProperties

租户配置属性类,定义多租户相关配置:

java
@ConfigurationProperties(PropertiesConstants.TENANT)
public class TenantProperties {
    
    /**
     * 是否启用
     */
    private boolean enabled = true;
    
    /**
     * 租户隔离级别
     */
    private TenantIsolationLevel isolationLevel = TenantIsolationLevel.LINE;
    
    /**
     * 租户 ID 列名
     */
    private String tenantIdColumn = "tenant_id";
    
    /**
     * 请求头中租户 ID 键名
     */
    private String tenantIdHeader = "X-Tenant-Id";
    
    /**
     * 超级租户 ID
     */
    private Long superTenantId = 1L;
    
    /**
     * 忽略表(忽略拼接多租户条件)
     */
    private List<String> ignoreTables;
}

TenantIsolationLevel

租户隔离级别枚举:

java
public enum TenantIsolationLevel {
    
    /**
     * 行级
     */
    LINE,
    
    /**
     * 数据源级
     */
    DATASOURCE
}

TenantContext

租户上下文,存储当前租户的相关信息:

java
public class TenantContext {
    
    /**
     * 租户 ID
     */
    private Long tenantId;
    
    /**
     * 隔离级别
     */
    private TenantIsolationLevel isolationLevel;
    
    /**
     * 数据源信息
     */
    private TenantDataSource dataSource;
}

TenantContextHolder

租户上下文 Holder,基于 ThreadLocal 实现线程级别的租户上下文管理:

java
public class TenantContextHolder {
    
    private static final TransmittableThreadLocal<TenantContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
    
    /**
     * 设置上下文
     */
    public static void setContext(TenantContext context);
    
    /**
     * 获取上下文
     */
    public static TenantContext getContext();
    
    /**
     * 获取租户 ID
     */
    public static Long getTenantId();
    
    /**
     * 清除上下文
     */
    public static void clearContext();
}

DefaultTenantLineHandler

默认租户行级隔离处理器,实现 MyBatis Plus 的 TenantLineHandler 接口:

java
public class DefaultTenantLineHandler implements TenantLineHandler {
    
    @Override
    public Expression getTenantId() {
        Long tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null) {
            return new LongValue(tenantId);
        }
        return null;
    }
    
    @Override
    public String getTenantIdColumn() {
        return tenantProperties.getTenantIdColumn();
    }
    
    @Override
    public boolean ignoreTable(String tableName) {
        // 超级租户忽略租户隔离
        Long tenantId = TenantContextHolder.getTenantId();
        if (tenantId != null && tenantId.equals(tenantProperties.getSuperTenantId())) {
            return true;
        }
        // 数据源级隔离时忽略行级处理
        if (TenantIsolationLevel.DATASOURCE.equals(TenantContextHolder.getIsolationLevel())) {
            return true;
        }
        // 检查忽略表配置
        return CollUtil.contains(tenantProperties.getIgnoreTables(), tableName);
    }
}

核心依赖

依赖描述
top.continew.starter:continew-starter-extension-tenant-core多租户 - 核心模块
com.baomidou:mybatis-plus-jsqlparserMyBatis Plus jsqlparser 适配(包含原扩展模块内容)
com.baomidou:dynamic-datasource-spring-boot3-starterDynamic Datasource(基于 Spring Boot 的快速集成多数据源的启动器)