前言
Mybatis并没有自带分页,为什么?这点读者可以自行思考一下。但我们项目里咋能没有分页呢。分页又因不同数据库分页的SQL不一样。就像安卓和IOS一样。这时候我们就自行建一个自动分页插件可以吗,答案是可以的。网上的各种分页插件原理大同小异,核心都是一样的。我们来分析一下。
目标
- 理解分页原理
- 自行实现一个分页插件
- 插件能扩展不同数据库(不可能把所有数据库都实现一遍,给使用者一定的自定义数据库分页)
思路
首先我们要明白Mybatis给我们提供一个插件org.apache.ibatis.plugin.Interceptor,一个在执行过程中可以进行拦截调用的插件。这个是特别符合我们的预期的,因为我们也是要做一个拦截,然后进行分页,一个是改动SQL,二是查询总数。这是我们分页要做的事情。好的,废话不多说,上代码:
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
// 如果不是查询就不分页
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
return invocation.proceed();
}
//分页中。。。
//分页后继续执行
return invocation.proceed();
}
}
从以上代码可以看出,我们通过Mybatis提供的插件机制初步实现了拦截调用。但具体核心还没有做。目前比较流行的分页插件框架里原理都是类似的。那好,我们来模拟JPA实现分页:传入一个分页参数就分页,不转入就不分页。这样我们在分页与不分页切换也很简单,几乎没有多余的代码。
//分页实现
// 获得原始SQL,我们要对原始SQL进行改造,加上分页参数
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
//因为不能限制使用者在传入分页参数在第几个,我们用一个特殊参数RowBounds
//如果传入了RowBounds或其子类的实例,我们可以直接拿到
RowBounds rowBounds = (RowBounds) metaObject.getValue("delegate.rowBounds");
//假设我们扩展了RowBounds,是对RowBounds的扩充类Pagination
if (rowBounds instanceof Pagination) {
Pagination page = (Pagination) rowBounds;
//查询总数的SQL
String sqlCount = "select count(1) from (" + originalSql + ") t";
//拿到Connection可以执行查询总数SQL
Connection connection = (Connection) invocation.getArgs()[0];
//改变SQL,以mysql为例, 参数应从page里获得,我这里直接写死
originalSql = originalSql + " limit 0, 10";
//设置SQL,让Mybatis继续执行,但SQL已经被我们替换了
metaObject.setValue("delegate.boundSql.sql", originalSql);
}
以上就是分页实现的过程,这里我们已经实现了最原始的分页。如果传入Pagination这个类的参数,我们就会自动分页,如果没传就不分页。有人会问,这个参数需要自己构建吗?如果是自己写demo是要的,如果是项目里一般是通过接口传过来,会自动生成此对象,我们只要做为参数传入就可以了。
好了,写到了这里,我们只是实现一点点。还有好多没有实现:比如如何实现不同数据库自动切换分页SQL,如何实现给用户扩展不同数据库的分页。这里就不再一一叙述了。代码已开源,文档已提供:
源码: github.com/zhouxx/boot… 模块之boot-plus-mybatis-jpa
文档: zhouxx.github.io/boot-plus/#…
欢迎提出问题一起讨论。