mybatis-plus多租户处理器使用及避坑

一、介绍

MybaitsPlus多租户处理器是一个对于多租户问题的解决方案,主要的方案就是使用jSqlParser对sql进行解析,然后拼接租户id来实现多个租户之间的隔离,并且在删除、添加、修改和查询等操作时都会拼接租户ID

如:

SELECT * FROM info

处理后:

SELECT * FROM info WHERE tenant_id = 'tenant_id'

二、使用

以下复制的官方的demo,官方demo地址:https://gitee.com/baomidou/mybatis-plus-samples/tree/master/mybatis-plus-sample-tenant

package com.baomidou.mybatisplus.samples.tenant.config;

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;

/**
 * @author miemie
 * @since 2018-08-10
 */
@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() {
                return new LongValue(1);
            }

            // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
            @Override
            public boolean ignoreTable(String tableName) {
                return !"user".equalsIgnoreCase(tableName);
            }
        }));
        // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
        // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
//        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

是不是小伙伴们感觉仅仅是关键的一个配置文件就可以搞定了,用多租户处理器之后就再也不用自己手动进行租户ID的拼接了,那接下来跟大家说一下避坑的地方。

三、避坑

1、语法

在介绍中跟各位小伙伴们说了mybatis-plus使用的是jsqlparser来实现的,mybatis-plus使用jsqlparser来对语法进行解析并且使用jsqlparser来进行拼接租户id的。

然而jsqlparser是对于sql的规范来进行解析的,如果出现一些特别的语法。

如示例:

SELECT CAST('1' as SIGNED) 

经常用mysql的小伙伴们应该很熟悉这是一个字符串转换成数字的,而这种的jsqlparser就会报sql语法解析的一些错误,所以希望大家使用别的方法来替代如:

SELECT CONVERT('1', SIGNED)

2、表名问题

在使用多租户处理器的时候往往很多表是不需要进行租户id判断的,这里作者的处理方式是将不需要租户id判断的表放到配置文件中,并且使用以下进行判断。

    @Value("${system.tenant.not-tenant-tables:null}")
    private TreeSet<String> notTenantIdTable;

    @Override
    public boolean doTableFilter(String tableName) {
        return notTenantIdTable.contains(tableName.toLowerCase());
    }

这里看上去没啥问题,但是需要注意表名的大小写,以及``这种转义符号

3、insert sql问题

作者在使用多租户处理器的时候是在项目后期阶段,这时候大部分业务代码已经写完了,插入sql都是自己手动获取并且插入的,但是mybatis-plus的多租户处理器已经想到这个问题了,启用多租户处理器的时候就会自动插入租户ID,你自己插入了租户ID,多租户处理器也给你插入了租户ID就会出现以下sql:

INSERT INTO info(id, tenant_id, tenant_id) VALUES(1, 'tenant_id', 'tenant_id')

这时候mysql就会毫不留情的报错,解决这个的办法其实也很简单,不插入租户ID了呗,当然也可以用以下办法解决

1、继承TenantSqlParser

/**
 * 多租户sql解析器扩展
 * @author weicong
 * @version 1.0 createTime 2021-2-22 20:05:58
 */
public class TenantSqlParserExt extends TenantSqlParser {

    /**
     * 是否处理插入的sql
     */
    private boolean isHandlerInsert = true;

    /**
     * 是否处理查询的sql
     */
    private boolean isHandlerSelect = true;

    @Override
    public void processInsert(Insert insert) {
        if(isHandlerInsert){
            int index = findTenantIdColumnIndex(insert.getColumns());
            //若不存在租户ID则进行添加租户ID信息
            if(-1 == index){
                super.processInsert(insert);
            }
        }
    }

    /**
     * 获取Column集合中租户ID的位置,若没有返回-1
     * @param columns
     * @return
     */
    public int findTenantIdColumnIndex(List<Column> columns){
        Column column;
        for(int i = 0; i < columns.size(); i++){
            column = columns.get(i);
            if(Global.TENANT_ID_COLUMN.equals(column.getColumnName())){
                return i;
            }
        }
        return -1;
    }

    @Override
    public void processSelectBody(SelectBody selectBody) {
        if(isHandlerSelect){
            super.processSelectBody(selectBody);
        }
    }

    public boolean isHandlerInsert() {
        return isHandlerInsert;
    }

    public void setHandlerInsert(boolean handlerInsert) {
        isHandlerInsert = handlerInsert;
    }

    public boolean isHandlerSelect() {
        return isHandlerSelect;
    }

    public void setHandlerSelect(boolean handlerSelect) {
        isHandlerSelect = handlerSelect;
    }
}

2、配置TenantSqlParser

/**
     * 初始化多租户处理器
     * @param paginationInterceptor
     */
    public void initTenantHandler(PaginationInterceptor paginationInterceptor){
        ArrayList<ISqlParser> sqlParsers = new ArrayList<>();
        TenantSqlParser tenantSqlParser = new TenantSqlParserExt();
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId(boolean where) {
                return new LongValue(LoginUserUtils.getTenantId());
            }

            @Override
            public String getTenantIdColumn() {
                return Global.TENANT_ID_COLUMN;
            }

            @Override
            public boolean doTableFilter(String tableName) {
                tableName = clearSymbol(tableName);
                boolean is = tenantProperties.getNotTenantIdTable().contains(tableName.toLowerCase());
                return is;
            }
        });
        sqlParsers.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(sqlParsers);
    }

四、总结

本文已经介绍了介绍了mybaits-plus多租户处理器的使用以及避坑指南,作者已经整理了完整demo来提供大家参考,如果有问题请大家评论或进入Q群415777345找作者。本文纯手写。

gitee:https://gitee.com/nightowl7/mybatis-plus-tenant-demo

### 关于 Spring Boot 和 MyBatis-Plus 实现多租户架构的设计 在构建基于 Spring Boot 和 MyBatis-Plus多租户应用时,主要通过自定义 SQL 解析器来实现不同租户的数据隔离。具体来说,在每次查询数据库前,系统会根据当前用户的 `tenantId` 自动为每条 SQL 添加相应的过滤条件[^1]。 对于多租户的支持,MyBatis-Plus 提供了一个名为 `MultiTenantHandler` 接口,允许开发者指定如何处理多租户逻辑。通常情况下,这涉及到设置默认的租户 ID 并将其应用于所有的 CRUD 操作中。此外,还可以利用 MyBatis-Plus 的全局配置能力,使得整个项目中的所有实体类都能继承相同的多租户行为而不需要重复编码[^5]。 为了更好地理解这一过程,下面是一个简单的代码片段展示了如何配置一个多租户处理器: ```java // 定义一个多租户插件 public class TenantInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement)invocation.getArgs()[0]; BoundSql boundSql = ms.getBoundSql(invocation.getArgs()[1]); String sql = boundSql.getSql(); // 假设我们有一个方法可以获取到当前请求对应的 tenantId Long tenantId = getCurrentTenantId(); if(tenantId != null){ // 对原始SQL进行加工, 加入tenant_id作为where条件的一部分 sql += " AND tenant_id=" + tenantId; } return invocation.proceed(); } private Long getCurrentTenantId(){ // 这里应该返回实际业务逻辑中用于区分不同租户的信息 return ThreadLocalUtils.getTenantId(); } } ``` 此段代码实现了对原有 SQL 查询语句动态增加 `AND tenant_id=xxx` 条件的功能,从而达到按租户划分数据的效果。值得注意的是,上述例子仅提供了一种可能的技术路线,并不代表唯一解决方案。不同的应用场景可能会有不同的需求和技术选型。 另外,考虑到微服务体系下的高可用性和扩展性,建议采用如 Eureka 或 Nacos 等服务发现机制配合 Ribbon 或者 Hystrix 组件来进行流量管理和熔断保护,确保即使某个节点出现问题也不会影响整体系统的稳定性[^3]。 最后,关于具体的架构图绘制,虽然无法直接给出图形化表示,但从文字描述上来看,理想的多租户架构应当具备清晰的服务层划分和服务间通信协议设计,同时要充分考虑安全性、性能优化以及后期运维便利等因素[^4]。
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值