错误用法:
添加插件时,先添加 分页插件,在添加租户插件。
正确用法:
添加插件时,先添加 租户插件,在添加 分页插件。
原理解析:
前提知识:
MP中所有的内置插件都必须实现 InnerInterceptor ,InnerInterceptor 中定义了拦截点。因为Mp的插件体系是这样的:
1. MybatisPlusInterceptor:起到执行者作用,管理了所有的内置插件。它会让所有的内置插件应用到本次的sql流程中。
2. InnerInterceptor 是所有内置插件的顶级父接口,它将Executor 和 Statment的拦截点 化简成了 许多抽象方法。 内置插件想在哪里做拦截,就重写对应的抽象方法。
本文中主要关注InnerInterceptor的如下两个抽象方法:
public interface InnerInterceptor {
/**
* 判断是否执行 {@link Executor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)}
* <p>
* 如果不执行query操作,则返回 {@link Collections#emptyList()}
*
* @param executor Executor(可能是代理对象)
* @param ms MappedStatement
* @param parameter parameter
* @param rowBounds rowBounds
* @param resultHandler resultHandler
* @param boundSql boundSql
* @return 新的 boundSql
*/
default boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
return true;
}
/**
* {@link Executor#query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql)} 操作前置处理
* <p>
* 改改sql啥的
*
* @param executor Executor(可能是代理对象)
* @param ms MappedStatement
* @param parameter parameter
* @param rowBounds rowBounds
* @param resultHandler resultHandler
* @param boundSql boundSql
*/
default void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// do nothing
}
}
以下快速过一下分页,多租户插件的作用。
分页插件运作原理
分页插件的 willDoQuery() :根据原始业务sql ,搞出一个 统计sql (select count(*) ...) . 将查询出来的total 设置到 分页对象page中。total>0 return true; 否者return false;
分页插件的 beforeQuery() :将limit xx,xx 拼接上 原始业务sql上。
多租户插件运作原理:
多租户插件的 willDoQuery() :永远return true;
多租户插件的 beforeQuery() :将 租户id 条件拼接上 业务sql中。
整体串联
当你使用mp进行分页查询时,首先被MybatisPlusInterceptor总执行器拦截。内部根据拦截体系点 应用所有添加的mp内置插件(分页,多租户...)
问题定位
这里假设 我们业务是需要执行如下代码
xxxMapper.page(new Page(pageNo,pageSize),queryWrapper);
如果我们当前是错误用法(即分页插件先添加,多租户插件在添加)
那么MybatisPlusInterceptor的逻辑中就先走分页插件,再走多租户插件
重点就在于:此时分页插件的willDoQuery() 计算total时使用的sql 就是select count(*) from table.... 就没有带上租户id,所以此时分页查询的 total 这个就计算不对!!!
紧接着走 query.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql); 这里面也没有将租户id 拼接上 业务sql ,仅仅只是为业务sql进化成了分页sql。(即加上了limit xx,xx 到sql 上面。)
好在于,第二次循环是租户插件,租户插件的willDoPagequery() 永远return true。所以第二次循环直接走 query.beforeQuery(...) 将租户id条件 弥补式的拼接上了 分页sql 上。
最终你才会看到分页执行时,records命中的记录没问题,但是total总数计算的不对!!
如果我们当前是正确用法(即多租户插件先添加,分页插件在添加)
我相信大家阅读到这里,都不用我解释了。
首先第一次循环,多租户插件 query.beforeQuery(...) 将租户条件拼接上 业务sql上。(假设sql现在:select xxxx from table where xxx=xxx and ... and 租户id=xxx)
紧接着,第二次循环应用分页插件,分页插件先走 query.willDoQuery(...) 这一次统计sql 已经有了租户id条件了,就不会出错了,计算出来的total也是正确的。 接着走 query.beforeQuery(..)就更不会出错了。