某j,mybatis-plus,多租户,多表关联查询 ,主表不追加租户条件bug解决
1.原因
经过半天一点点的手撕源码自己重写了TenantLineHandler,但是又重复加了,到这时才意识某j已经重了TenantLineHandler再排查发下 他重写的 ignoreTable(String tableName)中传过来的tableName多加了反引号`,在比较前去掉就好了
2.多租户配置类代码
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency>
package org.jeecg.config.mybatis;import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.TenantConstant;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.ArrayList;
import java.util.List;
import java.util.Objects;/*** 单数据源配置(jeecg.datasource.open = false时生效)* @Author zhoujf**/
@Configuration
@MapperScan(value={"org.jeecg.modules.**.mapper*"})
public class MybatisPlusSaasConfig {/*** 是否开启系统模块的租户隔离* 控制范围:用户、角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告** 实现功能* 1.用户表通过硬编码实现租户ID隔离* 2.角色、部门、我的部门、字典、分类字典、多数据源、职务、通知公告除了硬编码还加入的 TENANT_TABLE 配置中,实现租户隔离更安全* 3.菜单表、租户表不做租户隔离* 4.通过拦截器MybatisInterceptor实现,增删改查数据 自动注入租户ID*/public static final Boolean OPEN_SYSTEM_TENANT_CONTROL = true;/*** 哪些表需要做多租户 表需要添加一个字段 tenant_id*/public static final List<String> TENANT_TABLE = new ArrayList<String>();static {//1.需要租户隔离的表请在此配置if (MybatisPlusSaasConfig.OPEN_SYSTEM_TENANT_CONTROL) {//a.系统管理表// TENANT_TABLE.add("sys_user");TENANT_TABLE.add("sys_role");TENANT_TABLE.add("crm_sys_config");TENANT_TABLE.add("sys_queue");//TENANT_TABLE.add("sys_announcement");}//2.示例测试//TENANT_TABLE.add("demo");//3.online租户隔离测试//TENANT_TABLE.add("ceapp_issue");}@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptorinterceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {@Overridepublic Expression getTenantId() {String tenantId = TenantContext.getTenant();//如果通过线程获取租户ID为空,则通过当前请求的request获取租户(shiro排除拦截器的请求会获取不到租户ID)if(oConvertUtils.isEmpty(tenantId)){try {tenantId = TokenUtils.getTenantIdByRequest(SpringContextUtils.getHttpServletRequest());} catch (Exception e) {//e.printStackTrace();}}if(oConvertUtils.isEmpty(tenantId)){tenantId = "0";}return new LongValue(tenantId);}@Overridepublic String getTenantIdColumn(){return TenantConstant.TENANT_ID_TABLE;}// 返回 true 表示不走租户逻辑@Overridepublic boolean ignoreTable(String tableName) {tableName=tableName.replace("`","");for(String temp: TENANT_TABLE){if(temp.equalsIgnoreCase(tableName)){//判断是否使用注解不走多租户 //此文章中有MybatisTenantContext详细代码https://blog.csdn.net/zhaofuqiangmycomm/article/details/144106532if (Objects.nonNull(MybatisTenantContext.get())){return MybatisTenantContext.get();}return false; // 其他表进行租户过滤}}return true; // 返回 true,表示不对这些表进行租户过滤}}));//update-begin-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor());//update-end-author:zyf date:20220425 for:【VUEN-606】注入动态表名适配拦截器解决多表名问题interceptor.addInnerInterceptor(new PaginationInnerInterceptor());//【jeecg-boot/issues/3847】增加@Version乐观锁支持 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}/*** 动态表名切换拦截器,用于适配vue2和vue3同一个表有多个的情况,如sys_role_index在vue3情况下表名为sys_role_index_v3* @return*/private DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();dynamicTableNameInnerInterceptor.setTableNameHandler((sql, tableName) -> {//获取需要动态解析的表名String dynamicTableName = ThreadLocalDataHelper.get(CommonConstant.DYNAMIC_TABLE_NAME);//当dynamicTableName不为空时才走动态表名处理逻辑,否则返回原始表名if (ObjectUtil.isNotEmpty(dynamicTableName) && dynamicTableName.equals(tableName)) {// 获取前端传递的版本号标识Object version = ThreadLocalDataHelper.get(CommonConstant.VERSION);if (ObjectUtil.isNotEmpty(version)) {//拼接表名规则(原始表名+下划线+前端传递的版本号)return tableName + "_" + version;}}return tableName;});return dynamicTableNameInnerInterceptor;}}
3.手撕代码过程
1.找到类om.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor
中的processPlainSelect 这里是查询方法追加租户id的地方,再找到主表追加的地方
// 当有 mainTable 时,进行 where 条件追加 if (CollectionUtils.isNotEmpty(mainTables)) {plainSelect.setWhere(builderExpression(where, mainTables, whereSegment)); }
2.再看这个方法builderExpression 中构造每张表的查询条件
3.再点击 buildTableExpression中找到实现类
@Override public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {if (tenantLineHandler.ignoreTable(table.getName())) {return null;}return new EqualsTo(getAliasColumn(table), tenantLineHandler.getTenantId()); }
tenantLineHandler.ignoreTable( 是判断要不要走多租户的
4.再找到类tenantLineHandler,这里能发现mybatis-plus是默认所有表都走多租户的,
怎么配置让有的表走多租户,有的不走,就需要写一个配置类,继承tenantLineHandler,重写ignoreTable方法了,上面的MybatisPlusSaasConfig 是重写到内部类中去了,害我自己又写了一个
5.至此改掉 MybatisPlusSaasConfig中 的内部类MybatisPlusInterceptor 的ignoreTable中的比较bug问题解决,至于 tableName多加了反引号` 可能是某j的bug,有时候前端传过的参数也有多加反引号,有空还得好好排查下