当前使用版本(必填,否则不予处理)
3.4.3.4 (latest)
该问题是如何引起的?(确定最新版也有问题再提!!!)
现象: 多租户插件和分页插件同时配置的时候,分页的结果数据总记录数统计没有进行租户字段过滤,分页数据集合有进行租户字段过滤。
重现步骤(如果有就写完整)
代码片段:
//插件配置
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//阻止全表更新和删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
//多租户插件
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new MyTenantLine()));
return interceptor;
}
//行级租户处理器
public static class MyTenantLine implements TenantLineHandler{
private static ThreadLocal<Long> tenantId=new ThreadLocal<>();
public static void setTenantId(Long id){
tenantId.set(id);
}
/**
* 获取租户唯一标识,每次查询都从这里获取,然后进行过滤,
* 可从每次请求前端传入,这里模拟使用ThreadLocal获取
*/
@Override
public Expression getTenantId() {
return new LongValue(tenantId.get());
}
//获取租户唯一标识字段名,默认是tenant_id
@Override
public String getTenantIdColumn() {
return TenantLineHandler.super.getTenantIdColumn();
}
//控制哪些表需要过滤租户字段,哪些不需要过滤租户字段
@Override
public boolean ignoreTable(String tableName) {
return TenantLineHandler.super.ignoreTable(tableName);
}
}
执行sql打印:
==> Preparing: SELECT COUNT(*) FROM t_user WHERE deleted_at IS NULL -- 数据总记录统计查询sql
==> Preparing: SELECT id, name, password, created_at, deleted_at, gids, status FROM t_user WHERE deleted_at IS NULL AND tenant_id = 1 ORDER BY created_at DESC LIMIT ? -- 数据集合查询sql
结论: 数据总记录统计查询sql没有拼接上租户字段的过滤sql。
报错信息
从源码debug中查到以下方法可能有问题:
package com.baomidou.mybatisplus.extension.plugins.inner;
public class PaginationInnerInterceptor implements InnerInterceptor {
/**
* 这里进行count,如果count为0这返回false(就是不再执行sql了)
*/
@Override
public boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
if (page == null || page.getSize() < 0 || !page.isSearchCount()) {
return true;
}
BoundSql countSql;
MappedStatement countMs = buildCountMappedStatement(ms, page.countId());
if (countMs != null) {
/**todo 这里解析得到原始sql*/
countSql = countMs.getBoundSql(parameter);
} else {
countMs = buildAutoCountMappedStatement(ms);
/**todo 这里解析得到统计数据记录的sql: select COUNT(*) .....*/
String countSqlStr = autoCountSql(page.optimizeCountSql(), boundSql.getSql());
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
/**todo 这里重新构建出解析成数据统计后的sql:select COUNT(*) 的BoundSql对象*/
countSql = new BoundSql(countMs.getConfiguration(), countSqlStr, mpBoundSql.parameterMappings(), parameter);
PluginUtils.setAdditionalParameter(countSql, mpBoundSql.additionalParameters());
}
CacheKey cacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countSql);
/**todo 这里直接用executor进行查询,并没有进行租户字段处理的拦截,有问题!*/
Object result = executor.query(countMs, parameter, rowBounds, resultHandler, cacheKey, countSql).get(0);
page.setTotal(result == null ? 0L : Long.parseLong(result.toString()));
return continuePage(page);
}
}
Comment From: miemieYaho
看文档
Comment From: kim709394
使用多个功能需要注意顺序关系,建议使用如下顺序
多租户,动态表名 分页,乐观锁 sql性能规范,防止全表更新与删除 总结: 对sql进行单次改造的优先放入,不对sql进行改造的最后放入
https://mp.baomidou.com/guide/interceptor.html#mybatisplusinterceptor