mybatis-plus的分页/插件分析

时间:2025-04-27 10:14:24

使用mybatis-plus的分页方法。

文章目录

  • 一. 使用默认分页方法
  • 二. 使用分页插件
    • 2.1 分页插件配置
    • 2.2 创建分页对象
    • 2.3 使用示例
  • 三. 分页插件分析
  • 四. 总结

一. 使用默认分页方法

查看mybatis-plus(以下简称mp)源码中的BaseMapper接口,我们可以发现一下两个分页方法:

<E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);

<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);

使用mp写法,我们只需要在传入queryWrapper的基础上,再额外传入一个泛型限定为继承IPage接口的page属性即可。

而实际上,mp里已经给我们提供了一个默认实现Page类:

public class Page<T> implements IPage<T> {
    private static final long serialVersionUID = 8545996863226528798L;
    private List<T> records;
    private long total;
    private long size;
    private long current;
    private List<OrderItem> orders;
    private boolean optimizeCountSql;
    private boolean isSearchCount;
 	// ...   
}

即我们可以使用如下方法进行mp的分页查询:

Page<User> page = new Page();
// 当前页
page.setCurrent(1L);
// 最大条数
page.setSize(10L);
this.getBaseMapper().selectPage(page, new QueryWrapper<User>());

二. 使用分页插件

第一种情况是调用mp接口的时候可以快速实现分页,但是如果我们面临一些复杂的多表查询不能直接调用mp接口,而是需要写一些xml的mapper文件来进行实现,这个时候我们仍然想要使用mp的分页方法该如何实现呢。

2.1 分页插件配置

实现mp分页扩展必须使用PaginationInnerInterceptor插件,即新建一个MybatisPlusConfig配置类,并通过@Bean进行注入:

@Configuration
@Slf4j
public class MybatisPlusConfig {

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setLimit(-1);
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

2.2 创建分页对象

当我们在处理复杂sql的分页查询的时候,在原有的入参上再加上一个实现IPage接口的类即可,这里我们可以仿照mp默认的Page创建一个我们自己的BasePage:

public class BasePage<T> implements IPage<T> {

    private static final long serialVersionUID = 8545996863226528798L;

    /**
     * 查询数据列表
     */
    private List<T> list = new ArrayList<>();
    /**
     * 总数
     */
    private long totalRow = 0;

    /**
     * 每页显示条数,默认 15
     */
    private long pageSize = 15;

    /**
     * 当前页
     */
    private long pageNumber = 1;

    /**
     * 排序字段信息
     */
    private List<OrderItem> orders = new ArrayList<>();

    /**
     * 自动优化 COUNT SQL
     */
    private boolean optimizeCountSql = true;
    /**
     * 是否进行 count 查询
     */
    private boolean isSearchCount = true;

    /**
     * 额外数据
     */
    private Object extraData;


    public BasePage() {

    }
}

2.3 使用示例

于是我们就可以在自定义mapper的sql写法的时候将分页对象传入即可自动进行分页,而在xml里无需写任何的limit等分页关键词,mp会自动根据分页对象的参数自动帮我们进行分页。

public interface UserMapper extends BaseMapper<User> {

    // 测试分页插件
    BasePage<User> selectcUserPage(BasePage<Object> parse, @Param("label") Integer label);

}
<select id="selectcUserPage" resultType="">
    select * from user
    where
    <choose>
        <when test="label != null and label == 0">
            label = 0
        </when>
        <otherwise>
            label != 0
        </otherwise>
    </choose>
</select>

三. 分页插件分析

以上分页具体是如何实现的呢。我们就需要观察PaginationInterceptor插件的源码,实际上它本身就是一个拦截器。

分析PaginationInterceptor#intercept方法:

  • 只有查询才需要分页实现
if (SqlCommandType.SELECT == mappedStatement.getSqlCommandType() && StatementType.CALLABLE != mappedStatement.getStatementType()) {
    BoundSql boundSql = (BoundSql)metaObject.getValue("");
    Object paramObj = boundSql.getParameterObject();
    // ...
}
  • 判断入参是否有IPage属性,只有IPage属性入参才执行分页

    BoundSql boundSql = (BoundSql)metaObject.getValue("");
    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) {
    	// 执行分页拦截方法
    } else {
        // 否则放行
        return invocation.proceed();
    }
    
    • 处理分页逻辑

      // 有省略
      String originalSql = boundSql.getSql();
      String buildSql = concatOrderBy(originalSql, page);
      DialectModel model = DialectFactory.buildPaginationSql(page, buildSql, dbType, this.dialectClazz);
      
    • 处理查询总数

      if (page.isSearchCount()) {
          SqlInfo sqlInfo = SqlParserUtils.getOptimizeCountSql(page.optimizeCountSql(), this.countSqlParser, originalSql);
          this.queryTotal(sqlInfo.getSql(), mappedStatement, boundSql, page, connection);
          if (page.getTotal() <= 0L) {
              return null;
          }
      }
      

四. 总结

正常sql的limit分页逻辑我们已经司空见惯,但是对于mp里的默认分页方法进行深入分析有助于我们了解mp框架,实际上也是使用了拦截器的拦截功能,并进行相关的增强作用。

参考资料:

  • mybatis-plus分页查询详解
  • Java干货丨手撕MybatisPlus分页原理