Mybatis Plus 多租户方案

目录

一、Mybatis Plus 具体实现

二、生产示例


当不同的租户使用同一套程序,这里就需要考虑一个数据隔离的情况。

数据隔离有三种方案:

  1. 独立数据库:简单来说就是一个租户使用一个数据库,这种数据隔离级别最高,安全性最好,但是提高成本。
  2. 共享数据库、隔离数据架构:多租户使用同一个数据裤,但是每个租户对应一个Schema(数据库user)。
  3. 共享数据库、共享数据架构:使用同一个数据库,同一个Schema,但是在表中增加了租户ID的字段,这种共享数据程度最高,隔离级别最低。

一、Mybatis Plus 具体实现

Mybatis Plus 提供了一种多租户的解决方案,基于分页插件进行实现,具体实现代码如下:

1、租户配置 // 以下代码都是基于SpringBoot

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;

@Configuration
@MapperScan("com.baomidou.mybatisplus.samples.tenant.mapper")
public class MybatisPlusConfig {

    /**
     * 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 设置当前租户ID,实际情况你可以从cookie、或者缓存中拿都行
                return new LongValue(1);
            }
            @Override
            public String getTenantIdColumn() {
                // 对应数据库租户ID的列名
                return "tenant_id";
            }
            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                // 只对user表生效
                return !"user".equalsIgnoreCase(tableName);
            }
        }));
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // 用了分页插件必须设置 MybatisConfiguration#useDeprec
### MyBatis Plus 多租户实现方案 MyBatis Plus 提供了一种灵活的方式支持多租户功能。通过配置拦截器,可以在 SQL 执行前动态注入租户 ID,从而实现数据隔离[^1]。 #### 配置多租户拦截器 以下是实现多租户的具体步骤: 1. **创建 TenantHandler 类** 创建一个类继承 `MultiTenantHandler` 接口并重写方法来定义如何获取当前租户 ID。 ```java import com.baomidou.mybatisplus.extension.handlers.TenantLineHandler; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.StringValue; public class CustomTenantHandler implements TenantLineHandler { @Override public Expression getTenantId(boolean where) { String tenantId = CurrentUserContext.getTenantId(); // 获取当前用户的租户ID return new StringValue(tenantId); } @Override public String getTenantIdColumn() { return "tenant_id"; // 数据库中的租户字段名 } } ``` 2. **注册 MultiTenantInterceptor** 将自定义的 `CustomTenantHandler` 注册到 MyBatis 的拦截器链中。 ```java import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.MultiTenantInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new MultiTenantInnerInterceptor(new CustomTenantHandler())); return interceptor; } } ``` --- ### 定时任务与多租户集成 当使用定时任务(如 ScheduledExecutorService 或 Timer)时,在多租户场景下可能会遇到线程未绑定租户上下文的问题[^4]。这是因为定时任务运行在一个独立的线程池中,默认不会自动传递主线程的上下文信息。 #### 解决方案:手动绑定租户上下文 可以通过以下方式解决该问题: 1. **封装 TaskRunner 工具类** 使用工具类在执行定时任务之前显式设置租户上下文。 ```java import java.util.concurrent.Callable; public class TenantAwareTask<T> implements Callable<T> { private final String tenantId; private final Callable<T> task; public TenantAwareTask(String tenantId, Callable<T> task) { this.tenantId = tenantId; this.task = task; } @Override public T call() throws Exception { try { CurrentUserContext.setTenantId(tenantId); // 设置租户ID return task.call(); } finally { CurrentUserContext.clear(); // 清除上下文 } } } ``` 2. **修改定时任务逻辑** 调用上述工具类包装原始任务。 ```java import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class SchedulerExample { public static void main(String[] args) { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); Runnable task = () -> System.out.println("Executing with tenant: " + CurrentUserContext.getTenantId()); scheduler.scheduleAtFixedRate( new TenantAwareTask<>(null, () -> { task.run(); return null; }), 0, 1, TimeUnit.SECONDS); scheduler.shutdown(); } } ``` --- ### 延迟消息表的设计 如果需要进一步扩展定时任务的功能,比如处理延迟队列,则可以借助 MySQL 表结构存储待发送的消息,并配合轮询机制完成投递[^3]。 #### 数据库建表语句 ```sql CREATE TABLE `delay_msg` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `delivery_time` DATETIME NOT NULL COMMENT '投递时间', `payloads` blob COMMENT '消息内容', PRIMARY KEY (`id`), KEY `time_index` (`delivery_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; ``` #### 查询超时消息的任务 编写查询脚本定期扫描已到达投递时间的消息记录。 ```java @Scheduled(fixedDelay = 5000) public void processDelayedMessages() { List<DelayMsgEntity> overdueMessages = delayMsgMapper.selectList(Wrappers.<DelayMsgEntity>lambdaQuery() .le(DelayMsgEntity::getDeliveryTime, LocalDateTime.now())); for (DelayMsgEntity msg : overdueMessages) { handleMessage(msg.getPayloads()); // 发送或处理消息 delayMsgMapper.deleteById(msg.getId()); } } ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值