MyBatisPlus是怎么分页查询的

写在前面

由于目前的开发使用的ORM框架是MyBatis,作为一款优秀的半自动化ORM映射框架,Mybatis提供了灵活的sql语句的编写方式,但是过于灵活也使得所有的语句都需要自定义编写,例如通用的CURD操作也要从头编写一遍实际上从这一方面来看也会降低开发效率。而MyBatis-Plus就是为了简化Mybatis而生的。从名字不难看出Mybatis-Plus是Mybatis的加强版,它只对Mybatis的上层封装而不侵入现有工程,简单的配置可以让我们快速够建CRUD,其中分页功能更是极大的提高了开发效率。
点击查看MyBatis-Plus官网

快速使用

对于SpringBoot工程使用MyBatis-Plus非常简单,只需要在pom文件引入以下依赖即可集成进来

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.3.2</version>
</dependency>

内置分页插件分析

因为项目中很多地方用到了分页查询来展示数据到前端,在使用过程中不免产生对Mybatis-Plus分页插件的好奇,它是如何不用任何sql语句就几行代码就完成了我的分页操作呢?
首先来看看官网提供的分页插件使用方法

//Spring boot方式
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false
        // paginationInterceptor.setOverflow(false);
        // 设置最大单页限制数量,默认 500 条,-1 不受限制
        // paginationInterceptor.setLimit(500);
        // 开启 count 的 join 优化,只针对部分 left join
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}
public interface UserMapper {//可以继承或者不继承BaseMapper
    /**
     * <p>
     * 查询 : 根据state状态查询用户列表,分页显示
     * </p>
     *
     * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(你可以继承Page实现自己的分页对象)
     * @param state 状态
     * @return 分页对象
     */
    IPage<User> selectPageVo(Page<?> page, Integer state);
}

从以上 两段代码可以看出
一个是configuration作为MybatisPlus的配置里面生产一个PaginationInterceptor 的bean
另一个则是定义一个Mapper声明一个分页查询的接口,其中的返回类型为IPage类,参数类型为Page类型和Integer类型
后面的Mapper实现和Service层调用此处不再赘述,可以直接查看官网分页插件
由此咱们来大概猜想一下,分页插件的关键在于Page类和PaginationInterceptor (分页拦截器)
那么好了为了验证我们的猜想让我们来看一下其中的源码吧
此拦截器的位置在mybatis-plus-extension包中
PaginationInterceptor
打开看一下发现里面有intercept方法

public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        this.sqlParser(metaObject);
        MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()) {
            BoundSql boundSql = (BoundSql)metaObject.getValue("delegate.boundSql");
            Object paramObj = boundSql.getParameterObject();
            IPage<?> page = null;
            if (paramObj instanceof IPage) {
                page = (IPage)paramObj;
            } else if (paramObj instanceof Map) {
                Iterator var8 = ((Map)paramObj).values().iterator();

                while(var8.hasNext()) {
                    Object arg = var8.next();
                    if (arg instanceof IPage) {
                        page = (IPage)arg;
                        break;
                    }
                }
            }

            if (null != page && page.getSize() >= 0L) {
                if (this.limit > 0L && this.limit <= page.getSize()) {
                    this.handlerLimit(page);
                }

                String originalSql = boundSql.getSql();
                Connection connection = (Connection)invocation.getArgs()[0];
                if (page.isSearchCount() && !page.isHitCount()) {
                    SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql);
                    this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
                    if (page.getTotal() <= 0L) {
                        return null;
                    }
                }

                DbType dbType = (DbType)Optional.ofNullable(this.dbType).orElse(JdbcUtils.getDbType(connection.getMetaData().getURL()));
                IDialect dialect = (IDialect)Optional.ofNullable(this.dialect).orElse(DialectFactory.getDialect(dbType));
                String buildSql = concatOrderBy(originalSql, page);
                DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
                Configuration configuration = mappedStatement.getConfiguration();
                List<ParameterMapping> mappings = new ArrayList(boundSql.getParameterMappings());
                Map<String, Object> additionalParameters = (Map)metaObject.getValue("delegate.boundSql.additionalParameters");
                model.consumers(mappings, configuration, additionalParameters);
                metaObject.setValue("delegate.boundSql.sql", model.getDialectSql());
                metaObject.setValue("delegate.boundSql.parameterMappings", mappings);
                return invocation.proceed();
            } else {
                return invocation.proceed();
            }
        } else {
            return invocation.proceed();
        }
    }

这大概就是sql语句的拦截器吧,查看其逻辑不难发现

IPage<?> page = null;
            if (paramObj instanceof IPage) {
                page = (IPage)paramObj;
            } else if (paramObj instanceof Map) {
                Iterator var8 = ((Map)paramObj).values().iterator();

                while(var8.hasNext()) {
                    Object arg = var8.next();
                    if (arg instanceof IPage) {
                        page = (IPage)arg;
                        break;
                    }
                }
            }

在此处进行判断传入的参数是不是IPage类型,因为你要是分页你必须传一个Page类型的参数里面包含PageNo和PageSize
如果是Page类型并且pageSize大于0

if (null != page && page.getSize() >= 0L)
//此处判断是不是每页大于500,limit=500,大于500就取500
if (this.limit > 0L && this.limit <= page.getSize())

然后就是处理sql语句了

String originalSql = boundSql.getSql();
                Connection connection = (Connection)invocation.getArgs()[0];
                if (page.isSearchCount() && !page.isHitCount()) {
                    SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql);
                    this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
                    if (page.getTotal() <= 0L) {
                        return null;
                    }
                }
SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql);

这里就是将原始的sql语句originalSql封装成带count的语句
封装过程在SqlparserUtils中

public static SqlInfo getOptimizeCountSql(boolean optimizeCountSql, ISqlParser sqlParser, String originalSql) {
        if (!optimizeCountSql) {
            return SqlInfo.newInstance().setSql(getOriginalCountSql(originalSql));
        } else {
            if (null == COUNT_SQL_PARSER) {
                if (null != sqlParser) {
                    COUNT_SQL_PARSER = sqlParser;
                } else {
                    COUNT_SQL_PARSER = new JsqlParserCountOptimize();
                }
            }

            return COUNT_SQL_PARSER.parser((MetaObject)null, originalSql);
        }
    }

跟随调试器最后走到了

return COUNT_SQL_PARSER.parser((MetaObject)null, originalSql);

进入parser查看进入JsqlParserCountOptimize类

public SqlInfo parser(MetaObject metaObject, String sql)

其实现了ISqlParser类的parser方法

public SqlInfo parser(MetaObject metaObject, String sql) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("JsqlParserCountOptimize sql=" + sql);
        }

        SqlInfo sqlInfo = SqlInfo.newInstance();

        try {
            Select selectStatement = (Select)CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect)selectStatement.getSelectBody();
            Distinct distinct = plainSelect.getDistinct();
            GroupByElement groupBy = plainSelect.getGroupBy();
            List<OrderByElement> orderBy = plainSelect.getOrderByElements();
            if (null == groupBy && CollectionUtils.isNotEmpty(orderBy)) {
                plainSelect.setOrderByElements((List)null);
                sqlInfo.setOrderBy(false);
            }

            Iterator var9 = plainSelect.getSelectItems().iterator();

            while(var9.hasNext()) {
                SelectItem item = (SelectItem)var9.next();
                if (item.toString().contains("?")) {
                    return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(selectStatement.toString()));
                }
            }

            if (distinct == null && null == groupBy) {
                List<Join> joins = plainSelect.getJoins();
                if (this.optimizeJoin && CollectionUtils.isNotEmpty(joins)) {
                    boolean canRemoveJoin = true;
                    String whereS = (String)Optional.ofNullable(plainSelect.getWhere()).map(Object::toString).orElse("");
                    Iterator var12 = joins.iterator();

                    label54: {
                        String str;
                        String onExpressionS;
                        do {
                            if (!var12.hasNext()) {
                                break label54;
                            }

                            Join join = (Join)var12.next();
                            if (!join.isLeft()) {
                                canRemoveJoin = false;
                                break label54;
                            }

                            Table table = (Table)join.getRightItem();
                            str = (String)Optional.ofNullable(table.getAlias()).map(Alias::getName).orElse(table.getName()) + ".";
                            onExpressionS = join.getOnExpression().toString();
                        } while(!onExpressionS.contains("?") && !whereS.contains(str));

                        canRemoveJoin = false;
                    }

                    if (canRemoveJoin) {
                        plainSelect.setJoins((List)null);
                    }
                }

                plainSelect.setSelectItems(COUNT_SELECT_ITEM);
                return sqlInfo.setSql(selectStatement.toString());
            } else {
                return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(selectStatement.toString()));
            }
        } catch (Throwable var17) {
            return sqlInfo.setSql(SqlParserUtils.getOriginalCountSql(sql));
        }
    }

里面大概的逻辑就是提取原始sql并分解,去除查询的字段,保留查询条件,去除排序,最后填充count()计算行数
接着构建分页查询

DbType dbType = (DbType)Optional.ofNullable(this.dbType).orElse(JdbcUtils.getDbType(connection.getMetaData().getURL()));
                IDialect dialect = (IDialect)Optional.ofNullable(this.dialect).orElse(DialectFactory.getDialect(dbType));
                String buildSql = concatOrderBy(originalSql, page);
                DialectModel model = dialect.buildPaginationSql(buildSql, page.offset(), page.getSize());
  • 明确数据库类型
  • 确定数据库方言
  • 构建查询语句
  • 最后在DialectModel中可以看见sql变成了原始sql+limit ?,?这样的了

最后就是执行sql:
invocation.proceed()
执行的过程就不详细分析了,至此分页的原理就搞清楚了

总结

现在整个过程就很清楚了,让咱们总结一下

  1. 拦截器拦截sql语句以及查询参数
  2. 以Page类型为判断依据看看参数中是否包含Page类型参数
  3. 接着确定分页的参数包括对页大小做限制(每页最多500条)
  4. 传入原始sql构建一个count sql
  5. 根据数据库类型使用不同的数据库方言进行封装,此处mysql将原始sql封装加上limit达到分页效果
  6. 执行两个分页的sql并返回结果
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值