Mybatis plus中遇到的分页查询报错问题解决

文章讲述了在使用MybatisPlus进行分页查询时遇到的一个bug,即LEFTJOIN语句在执行时消失,导致查询错误。经过调试发现,这是由于MybatisPlus的分页拦截器PaginationInnerInterceptor默认优化了COUNTSQL,移除了JOIN操作。通过设置`optimizeJoin`为false,可以避免这个问题,确保在分页查询时JOIN语句不会被移除,从而解决问题。
摘要由CSDN通过智能技术生成

        本人有一天被领导调去其他维护其他系统,对于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即可。

        这样我们再运行分页查询,查询分页总数的时候,就再也不会出现报错的想象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sun cat

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值