使用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分页原理