本人有一天被领导调去其他维护其他系统,对于mybatis的相关配置我也没全程参与(注意这里就是根源的起头)。
修bug的时候,遇到一个及其诡异的bug:我的service层调用mapper.xml中的分页查询sql时,居然报错了!!!
一开始我还以为是我sql写错了,经检查没发现什么异常,于是就拿到navicat中跑一下sql,也能成功跑出数据。
我当时的sql类似这样:
select
t1.a,
t1.b,
t1.c,
t2.x,
t2.y,
t2.z
from user t1
left join role t2 on t1.id = t2.user_id
where
t1.b = "close"
and t2.z = 0
我不竟在怀疑:难道是service到mapper.xml这一层传参传错了?
于是立马上手打断点,结果发现,一切正常,无论是service到mapper.xml还是在把这些参数传入的mybatis plus里,这些参数都正常。
唯一异常之处就在于sql日志输出输出了个类似这样的sql,而且提示找不到t2表及其对应的字段。
select
count(*)
from user t1
where
t1.b = "close"
and t2.z = 0
其中select count(*)我可以理解为mybatis plus 分页查询数据前做的总数查询。
可我sql里明明设置了left join role t2 on t1.id = t2.user_id 语句,为什么到执行的时候却没有?
难道有什么地方把我sql拦截了并做了什么奇怪的骚操作?
我带着这个疑惑,打着代码断点,一步一步地调试代码逻辑,最后我在mybatis plus 中PaginationInnerInterceptor类分页拦截器看到这一个方法
/**
*获取自动优化的countSql
*@parampage参数
*@paramsqlsql
*@returncountSql
*/
protectedStringautoCountSql(IPage<?>page,Stringsql){
if(!page.optimizeCountSql()){
return lowLevelCountSql(sql);
}
try{
Select select = (Select)CCJSqlParserUtil.parse(sql);
SelectBody selectBody = select.getSelectBody();
//https://github.com/baomidou/mybatis-plus/issues/3920分页增加union语法支持
if(selectBody instanceof SetOperationList){
return lowLevelCountSql(sql);
}
PlainSelect plainSelect = (PlainSelect)select.getSelectBody();
Distinct distinct = plainSelect.getDistinct();
GroupByElement groupBy = plainSelect.getGroupBy();
List<OrderByElement> orderBy = plainSelect.getOrderByElements();
if(CollectionUtils.isNotEmpty(orderBy)){
booleancanClean = true;
if(groupBy != null){
//包含groupBy不去除orderBy
canClean=false;
}
if(canClean){
for(OrderByElement order:orderBy){
//orderby里带参数,不去除orderby
Expression expression = order.getExpression();
if(!(expression instanceof Column) && expression.toString().contains(StringPool.QUESTION_MARK)){
canClean=false;
break;
}
}
}
if(canClean){
plainSelect.setOrderByElements(null);
}
}
//#95Github,selectItemscontains# {}${},whichwillbetranslatedto?,anditmaybeinafunction:power(#{myInt},2)
for(SelectItem item : plainSelect.getSelectItems()){
if(item.toString().contains(StringPool.QUESTION_MARK)){
return lowLevelCountSql(select.toString());
}
}
//包含distinct、groupBy不优化
if(distinct != null || null != groupBy){
return lowLevelCountSql(select.toString());
}
//包含join连表,进行判断是否移除join连表
if(optimizeJoin && page.optimizeJoinOfCountSql()){
List<Join> joins = plainSelect.getJoins();
if(CollectionUtils.isNotEmpty(joins)){
boolean canRemoveJoin = true;
String whereS = Optional.ofNullable(plainSelect.getWhere()).map(Expression::toString).orElse(StringPool.EMPTY);
//不区分大小写
whereS = whereS.toLowerCase();
for(Join join : joins){
if(!join.isLeft()){
canRemoveJoin = false;
break;
}
FromItem rightItem = join.getRightItem();
Stringstr = "";
if(rightItem instanceof Table){
Tabletable = (Table) rightItem;
str = Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName())+StringPool.DOT;
}else if(rightItem instanceof SubSelect){
SubSelect subSelect = (SubSelect) rightItem;
/*如果leftjoin是子查询,并且子查询里包含?(代表有入参)或者where条件里包含使用join的表的字段作条件,就不移除join*/
if(subSelect.toString().contains(StringPool.QUESTION_MARK)){
canRemoveJoi = false;
break;
}
str = subSelect.getAlias().getName() + StringPool.DOT;
}
//不区分大小写
str = str.toLowerCase();
if(whereS.contains(str)){
/*如果where条件里包含使用join的表的字段作条件,就不移除join*/
canRemoveJoin = false;
break;
}
for(Expression expression : join.getOnExpressions()){
if(expression.toString().contains(StringPool.QUESTION_MARK)){
/*如果join里包含?(代表有入参)就不移除join*/
canRemoveJoin = false;
break;
}
}
}
if(canRemoveJoin){
plainSelect.setJoins(null);
}
}
}
//优化SQL
plainSelect.setSelectItems(COUNT_SELECT_ITEM);
returnselect.toString();
}catch(JSQLParserExceptione){
//无法优化使用原SQL logger.warn("optimizethissqltoacountsqlhasexception,sql:\""+sql+"\",exception:\n"+e.getCause());
}catch(Exceptione){
logger.warn("optimizethissqltoacountsqlhaserror,sql:\""+sql+"\",exception:\n"+e);
}
return lowLevelCountSql(sql);
}
看到这个方法,相信大家也就明白了。程序中会根据optimizeJoin变量来决定是否移除join连表,而且这个值默认就是true,也就是默认会移除!!!
这样就能解释为什么"sql里明明设置了left join role t2 on t1.id = t2.user_id 语句,为什么到执行的时候却没有"的情况。
我们知道这个原因之后,我们只需要设置
设置该变量的值为false即可。
这样我们再运行分页查询,查询分页总数的时候,就再也不会出现报错的想象