概述
MyBatis常见的分页有如下几种:
- 使用RowBounds逻辑分页
- 在SQL语句中使用limit分页
- 使用自定义拦截器分页
- 使用PageHelper插件分页
使用RowBounds逻辑分页
Mybatis可以通过传递RowBounds对象,来进行数据库数据的分页操作,然而遗憾的是,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。
所谓的物理分页是指在SQL语句中使用limit进行分页。
先说如何使用RowBounds来完成分页操作:
控制器类就是直接调用service层的接口
需要注意的是RowBounds的两个参数,其中offset指的是表示从第几条开始,limit指的是获取多少条数据。
RowBounds的源码如下:
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = 2147483647;
public static final RowBounds DEFAULT = new RowBounds();
private final int offset;
private final int limit;
public RowBounds() {
this.offset = 0;
this.limit = 2147483647;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
public int getOffset() {
return this.offset;
}
public int getLimit() {
return this.limit;
}
}
service层没有什么需要注意的地方,将RowBounds作为参数即可
mapper层的代码如下,接口方法的参数仍然是RowBounds,但注意在mapper.xml中的SQL语句根查询所有记录的SQL语句一样,都是查询所有,如果带条件那么添加where语句即可,即在分页查询的SQL语句中没有对分页的处理。
注意:Mybatis的分页是对结果集进行的分页。
如果对分页原理感兴趣可以参考:Mybatis3.3.x技术内幕(十三):Mybatis之RowBounds分页原理
在SQL语句中使用limit分页
实际上,我们在分页中要动态设置分页参数,所以需要创建一个Page实体类
/**
* 分页的相关属性和方法
*
* @author lck100
*/
public class Page implements Serializable {
// 所有数据结果集
private List list;
// 每页显示多少条数据
private int pageSize;
// 页码(第几页)
private int pageIndex;
// 记录总条数
private int totalRecords;
/**
* 计算总的页面数
*
* @return 返回一共有多少页
*/
public int getTotalPages() {
if (totalRecords % pageSize == 0) {
// 如果记录总条数对每页显示记录数取整等于0,则表示页面数刚好分完。
return totalRecords / pageSize;
} else {// 如果取不尽,那么就添加一页来放剩余的记录
return totalRecords / pageSize + 1;
}
}
/**
* 获取首页的页码
*
* @return 返回首页的页码,即为1
*/
public int getFirstPageIndex() {
return 1;
}
/**
* 获取上一页的页码
*
* @return 返回上一页的页码,即当前页码减去1
*/
public int getPreviousPageIndex() {
if (pageIndex <= 1) {
return 1;
}
return pageIndex - 1;
}
/**
* 获取下一页的页码
*
* @return 返回下一页的页码,即当前页码加上1
*/
public int getNextPageIndex() {
if (pageIndex >= getLastPageIndex()) {
return getLastPageIndex();
}
return pageIndex + 1;
}
/**
* 获取最后一页的页码
*
* @return 返回最后一页的页码,即总页数
*/
public int getLastPageIndex() {
return getTotalPages();
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getPageIndex() {
return pageIndex;
}
public void setPageIndex(int pageIndex) {
this.pageIndex = pageIndex;
}
public int getTotalRecords() {
return totalRecords;
}
public void setTotalRecords(int totalRecords) {
this.totalRecords = totalRecords;
}
}
控制器类的代码如下:
注意,其中pageIndex和pageSize都需要从前端动态获取,并且页索引该从1开始,这里从0开始也只是为了演示使用,并且向前端应该返回page的所有对象。
service层的代码如下,没有什么注意的地方:
mapper层的代码如下:
注意:写SQL语句时必须使用指定limit。
使用自定义拦截器分页
首先创建一个自定义拦截器MyPageIntercptor.java
/**
* @Intercepts 说明是一个拦截器
* @Signature 拦截器的签名
* type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
* method 拦截的方法
* args 参数,高版本需要加个Integer.class参数,不然会报错
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})})
public class MyPageInterceptor implements Interceptor {
//每页显示的条目数
private int pageSize;
//当前现实的页数
private int pageIndex;
//数据库类型
private String dbType;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取StatementHandler,默认是RoutingStatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取statementHandler包装类
MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);
//分离代理对象链
while (MetaObjectHandler.hasGetter("h")) {
Object obj = MetaObjectHandler.getValue("h");
MetaObjectHandler = SystemMetaObject.forObject(obj);
}
while (MetaObjectHandler.hasGetter("target")) {
Object obj = MetaObjectHandler.getValue("target");
MetaObjectHandler = SystemMetaObject.forObject(obj);
}
//获取连接对象
//Connection connection = (Connection) invocation.getArgs()[0];
//object.getValue("delegate"); 获取StatementHandler的实现类
//获取查询接口映射的相关信息
MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
String mapId = mappedStatement.getId();
//statementHandler.getBoundSql().getParameterObject();
//拦截以.ByPage结尾的请求,分页功能的统一实现
if (mapId.matches(".+ByPage$")) {
//获取进行数据库操作时管理参数的handler
ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
//获取请求时的参数
Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
//也可以这样获取
//paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();
//参数名称和在service中设置到map中的名称一致
pageIndex = (Integer) paraObject.get("pageIndex");
pageSize = (Integer) paraObject.get("pageSize");
String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
//也可以通过statementHandler直接获取
//sql = statementHandler.getBoundSql().getSql();
//构建分页功能的sql语句
String limitSql;
sql = sql.trim();
limitSql = sql + " limit " + (pageIndex - 1) * pageSize + "," + pageSize;
//将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日
MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
}
//调用原对象的方法,进入责任链的下一级
return invocation.proceed();
}
//获取代理对象
@Override
public Object plugin(Object o) {
//生成object对象的动态代理对象
return Plugin.wrap(o, this);
}
//设置代理对象的参数
@Override
public void setProperties(Properties properties) {
//如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
String limit1 = properties.getProperty("limit", "10");
this.pageSize = Integer.valueOf(limit1);
this.dbType = properties.getProperty("dbType", "mysql");
}
}
然后在mybatis的配置文件中将拦截器类配置进去:
<!--配置自定义的拦截器插件-->
<plugins>
<plugin interceptor="com.demo.interceptor.MyPageInterceptor">
<property name="limit" value="10"/>
<property name="dbType" value="mysql"/>
</plugin>
</plugins>
控制器controller层代码:
service层代码如下:
注意:必须在service层使用Map集合来放参数,如果有条件也需要通过map集合来存放,并且mapper层的方法名必须是xxxByPage()。
mapper层代码如下:
注意:方法名的命名必须是xxxByPage(),参数是Map类型,而SQL语句也是查询所有。
使用PageHelper插件分页
首先需要导入相关依赖(这里使用maven工程):
<!--mybatis-pagehelper分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
接着在mybatis的配置文件中添加如下配置:
<!--配置mybatis-pagehelper插件-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用MySQL方言的分页 -->
<property name="helperDialect" value="mysql"/><!--如果使用mysql,这里value为mysql-->
<property name="pageSizeZero" value="true"/>
</plugin>
</plugins>
然后就可以使用了,控制器类代码示例如下:
注意:需要在调用service层方法时传入pageIndex和pageSize,也需要从前端动态获取,这里仅作演示。
service层中的代码如下:
注意:只需要在调用mapper层方法之前添加如下代码:
PageHelper.startPage(pageIndex,pageSize);
其中PageHelper.startPage()是固定格式的静态方法,传入的两个参数分别是页码和页记录条数。
最后是mapper层代码,SQL语句也不需要进行limit操作,也不需要传入页参数:
其实PageHelper方法也是使用Interceptor拦截器方式的一种三方实现,它内部帮助我们实现了Interceptor的功能。
所以我们不用自定义MyPageInterceptor这个类了。实际上也是在运行查询方法的时候,进行拦截,然后设置分页参数。所以PageHelper.startPage(pageIndex, pageSize)这一句需要显式调用,然后再执行userDao.findAll(),在查询所有用户信息的时候,会进行一个分页参数设置,让放回的结果只是分页的结果,而不是全部集合。