mybatis-plus 多租户方案1使用和坑注意事项,方案是需要实现租户功能的表都增加租户id字段

1 前提springboot整合好 mybatis-plus (版本3.5.4)
   需要实现多租户的表,添加修改对应字段和 pojo类 (表添加tenant_id字段,pojo添加tenantId属性)

配置文件更改,方便扩展

#多租户配置
tenant:
  enable: true
  column: tenant_id
  ignoreTables:
    - tb_order
    - users
  ignoreLoginNames:

属性文件配置类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;

/**
 * 多租户配置属性类
 */
@Component
@Data
@ConfigurationProperties(prefix = "tenant")
public class TenantProperties {
    /**
     * 是否开启多租户
     */
    private Boolean enable;

    /**
     * 租户id字段名
     */
    private String column;


    /**
     * 需要忽略的多租户的表,此配置优先filterTables,若此配置为空则启用filterTables
     */
    private List<String> ignoreTables;

    /**
     * 需要排除租户过滤的登录用户名
     */
    private List<String> ignoreLoginNames;

}

4 租户id管理类,从threadlocal 中取,这个根据业务情况来

/**
 * 租户上下文持有者,用于管理当前线程的租户ID。
 * 提供了获取、设置和清除当前线程租户ID的方法。
 */
public class TenantContextHolder {
    // 静态ThreadLocal变量,用于存储当前线程的租户ID
    static ThreadLocal<Long> tenantThreadLocal = new ThreadLocal<>();

    /**
     * 获取当前线程的租户ID。
     *
     * @return 当前线程的租户ID
     */
    public static Long getCurrentTenantId() {
        return tenantThreadLocal.get();
    }

    /**
     * 设置当前线程的租户ID。
     *
     * @param tenantId 要设置的租户ID
     */
    public static void setCurrentTenantId(Long tenantId) {
        tenantThreadLocal.set(tenantId);
    }

    /**
     * 清除当前线程的租户ID,使得线程不再关联任何租户。
     */
    public static void clear() {
        tenantThreadLocal.remove();
    }
}

多租户处理器实现,核心类

import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 多租户处理器实现,核心类
 */
@Component
public class MultiTenantHandler implements TenantLineHandler {

    @Autowired
    private TenantProperties tenantProperties;

    /**
     * 根据表名判断是否忽略拼接多租户条件   和  忽略指定的用户
     * 默认都要进行解析并拼接多租户条件
     *
     * @param tableName 表名
     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
     */
    @Override
    public boolean ignoreTable(String tableName) {
        //忽略指定表对租户数据的过滤
        List<String> ignoreTables = tenantProperties.getIgnoreTables();
        if (null != ignoreTables && ignoreTables.contains(tableName)) {
            return true;
        }

        //从ThreadLocal里面的安全上下文 中获取 用户名称
        String loginName=.......
        if(null!=ignoreLoginNames && ignoreLoginNames.contains(loginName)){
            return true;
        }

        return false;
    }

    /**
     * 获取租户ID值表达式,只支持单个ID值 (实际应该从用户信息中获取)
     * @return 租户ID值表达式
     */
    @Override
    public Expression getTenantId() {

        if(TenantContextHolder.getCurrentTenantId()!=null)
        {
            Long tenantId = TenantContextHolder.getCurrentTenantId();
            if(tenantId!=null)
            {
                return new LongValue(tenantId);
            }
        }
        //设默认的值 或 异常,灵活处理
        return new LongValue(0);
    }


    /**
     * 获取租户字段名,默认字段名叫: tenant_id
     * @return 租户字段名
     */
    @Override
    public String getTenantIdColumn() {
        return tenantProperties.getColumn();
    }

}

mybatis 拦截器配置,核心类

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {

    @Autowired
    private MultiTenantHandler tenantHandler;

    @Autowired
    private TenantProperties tenantProperties;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        if (tenantProperties.getEnable()) {
            // 添加多租户插件
            interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));
        }

        //分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        //防止全表更新与删除插件: BlockAttackInnerInterceptor
        BlockAttackInnerInterceptor blockAttackInnerInterceptor = new BlockAttackInnerInterceptor();
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor);

        return interceptor;
    }
}

不需要租户的方法 (此注解只能放在mapper层的接口方法中),可以添加 如下配置,如,生成的sql就不会拼接租户的字段

@InterceptorIgnore(tenantLine = "true")
List<Book> searchAllBook_diySql();

写个springboot 拦截器或用其它方案,如拦截器里,将header中的租户id,设置到TenantContextHolder.setCurrentTenantId里面去

开始测试,需要使用租户功能的,在header中添加 租户id
在增删改查操作时,sql都会拼接上 租户id的条件




坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑坑


1 自己写的sql ,不要加租户id字段,不要加租户id字段,不要加租户id字段,系统会自己拼接sql

这个示例,租户id都是统一添加在header中,拦截器再从header中取租户id,并设置到TenantContextHolder.setCurrentTenantId里面去,某些方法又在参数里如 requestParam或RequestBody中设置了租户id,会引发数据错乱,

方法1 这边是使用了一个aop拦截器,将参数中传递的租户id,统一设为null,如下

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;

@Aspect
@Component
public class TenantIdAspect {

    @Before("execution(* cn.baidu.sharding_jdbc_test.control..*.*(..)) && args(*)")
    public void cleanTenantId(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            // 使用反射来设置租户ID为null
            if (arg != null) {
                try {
                    String fieldname = "tenantId"; // 假设所有实体类中租户ID的字段名为tenantId
                    Field field = arg.getClass().getDeclaredField(fieldname);
                    field.setAccessible(true);
                    field.set(arg, null);
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    // 处理异常,例如记录日志
                    e.printStackTrace();
                }
            }
        }
    }
}

方法2,参数传递时不要加租户id,这样pojo可能会建好几个类似的,根据不同的方法,传递相应的pojo即可


部分细节处理参考博文

https://www.cnblogs.com/crazymakercircle/p/17923774.html

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值