如果你也在用 MyBatis,建议尝试该分页插件,这一定是最方便使用的分页插件。

分页插件的必要性

互联网应用中,分页可谓无处不在,在每个需要展示数据的地方,都能找到分页的影子。在日常开发中,为了追求效率,通常使用数据库的物理分页。这时,对于一个业务逻辑SQL,大多数情况需要输出两段SQL来达到分页效果:count查询总数和limit分页,这无疑增加了大量的工作量。对于这种大量的、相似的、非业务逻辑的代码,抽象出公共插件是势在必行的。

分页插件原理

Mybatis给开发者提供了一个拦截器接口,只要实现了该接口,就可以在Mybatis执行SQL前,作一些自定义的操作。分页插件就是在此基础上开发出来的,对于一个需要分页的SQL,插件会拦截并生成两段SQL。举一个简单的例子:

原SQL:

1select * from table where a = '1'

拦截后的查询总数SQL:

1select count(*) from table where a = '1'

拦截后的分页SQL:

1select * from table where a = '1' limit 5,10

这样我们只需要根据业务逻辑开发原SQL,不需关心分页语法对原SQL的影响,拦截器已经为我们处理好了。更多拦截器的信息可以参考:

支持的数据库

该插件目前支持以下数据库的物理分页:

  1. Oracle
  2. Mysql
  3. MariaDB
  4. SQLite
  5. Hsqldb
  6. PostgreSQL
  7. DB2
  8. SqlServer(2005,2008)
  9. Informix
  10. H2
  11. SqlServer2012
  12. Derby
  13. Phoenix

与SpringMVC集成

Maven依赖

1<!-- 分页插件 -->
2<dependency>
3    <groupId>com.github.pagehelper</groupId>
4    <artifactId>pagehelper</artifactId>
5    <version>5.0.0</version>
6</dependency>

Spring配置文件

只需要在原来配置Mybatis的SqlSessionFactoryBean的地方加上分页插件的配置即可,具体区别请看以下的对比:

原来的配置方式:

1<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2    <property name="dataSource" ref="dynamicDataSource"/>
3    <property name="mapperLocations">
4        <list>
5            <value>classpath:mapper/*.xml</value>
6            <value>classpath:mapper/*/*.xml</value>
7        </list>
8    </property>
9</bean>

加上分页插件的配置方式:

 1<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 2    <property name="dataSource" ref="dynamicDataSource"/>
 3    <property name="mapperLocations">
 4        <list>
 5            <value>classpath:mapper/*.xml</value>
 6            <value>classpath:mapper/*/*.xml</value>
 7        </list>
 8    </property>
 9    <!-- 配置分页插件 -->
10    <property name="plugins">
11        <array>
12            <bean class="com.github.pagehelper.PageInterceptor">
13                <property name="properties">
14                    <value>
15                        helperDialect=postgresql
16                        reasonable=true
17                    </value>
18                </property>
19            </bean>
20        </array>
21    </property>
22</bean>

可以看到仅仅是加了<property name="plugins">的配置。在<property name="properties">里可以配置分页参数,一般情况下配置数据库类型helperDialect即可。完整的参数如下:

 1<!-- 该参数默认为false -->
 2<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
 3<!-- 和startPage中的pageNum效果一样-->
 4<property name="offsetAsPageNum" value="true"/>
 5<!-- 该参数默认为false -->
 6<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
 7<property name="rowBoundsWithCount" value="true"/>
 8<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
 9<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
10<property name="pageSizeZero" value="true"/>
11<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
12<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
13<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
14<property name="reasonable" value="true"/>
15<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
16<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
17<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
18<property name="params" value="pageNum=start;pageSize=limit;pageSizeZero=zero;reasonable=heli;count=contsql"/>

在代码中使用

在需要进行分页的Mybatis方法前调用PageHelper.startPage静态方法即可,紧跟在这个方法后的第一个Mybatis查询方法会被进行分页,然后分页插件会把分页信息封装到PageInfo中。

1// startPage(第几页, 多少条数据)
2PageHelper.startPage(pageIndex, pageSize);
3// Mybatis查询方
4List<InstanceVO> list = instanceDao.select(instance);
5// 用PageInfo对结果进行包装
6PageInfo pageInfo = new PageInfo(list);

以这种对原SQL无侵害的方法,就可以得到分页的效果和详细的分页信息。PageInfo包含了非常全面的分页属性:

public class PageInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    //当前页
    private int pageNum;
    //每页的数量
    private int pageSize;
    //当前页的数量
    private int size;
    //由于startRow和endRow不常用,这里说个具体的用法
    //可以在页面中"显示startRow到endRow 共size条数据"
    //当前页面第一个元素在数据库中的行号
    private int startRow;
    //当前页面最后一个元素在数据库中的行号
    private int endRow;
    //总记录数
    private long total;
    //总页数
    private int pages;
    //结果集
    private List<T> list;
    //第一页
    private int firstPage;
    //前一页
    private int prePage;
    //下一页
    private int nextPage;
    //最后一页
    private int lastPage;
    //是否为第一页
    private boolean isFirstPage = false;
    //是否为最后一页
    private boolean isLastPage = false;
    //是否有前一页
    private boolean hasPreviousPage = false;
    //是否有下一页
    private boolean hasNextPage = false;
    //导航页码数
    private int navigatePages;
    //所有导航页号
    private int[] navigatepageNums;
    ...
}

具体的例子

原SQL:

1select
2    id,name,create_time,create_user_id,update_time,update_user_id,is_delete
3from xxx.aaa
4where
5    ( is_delete = ? )

拦截后的SQL(源自Mybatis日志信息):

1# count
2select count(0) from xxx.aaa WHERE (is_delete = ?)
1# 分页
2select
3    id,name,create_time,create_user_id,update_time,update_user_id,is_delete
4from xxx.aaa
5where
6    ( is_delete = ? ) LIMIT 3

返回的json数据: 返回数据用一个自定义类Result来封装,models是业务数据,paging是分页信息。

 1{
 2	"models":[
 3		{
 4			"createTime":1508890619000,
 5			"createUserId":"888888",
 6			"id":3,
 7			"isDelete":0,
 8			"name":"【TEST】",
 9			"updateTime":1512373972000,
10			"updateUserId":"888888"
11		},
12		{
13			"createTime":1508890619000,
14			"createUserId":"888888",
15			"id":4,
16			"isDelete":0,
17			"name":"bbb",
18			"updateTime":1508891132000,
19			"updateUserId":"888888"
20		},
21		{
22			"createTime":1508890619000,
23			"createUserId":"888888",
24			"id":5,
25			"isDelete":0,
26			"name":"ccc",
27			"updateTime":1508891132000,
28			"updateUserId":"888888"
29		}
30	],
31	"paging":{
32		"endRow":3,
33		"firstPage":1,
34		"hasNextPage":true,
35		"hasPreviousPage":false,
36		"isFirstPage":true,
37		"isLastPage":false,
38		"lastPage":5,
39		"navigateFirstPage":1,
40		"navigateLastPage":5,
41		"navigatePages":8,
42		"navigatepageNums":[1,2,3,4,5],
43		"nextPage":2,
44		"pageNum":1,
45		"pageSize":3,
46		"pages":5,
47		"prePage":0,
48		"size":3,
49		"startRow":1,
50		"total":15
51	},
52	"resultCode":"100",
53	"success":true,
54	"valid":true
55}

总结

使用分页插件时,不需要在分页的地方手写分页SQL和count的SQL,不需要更改已有的业务代码,只需要在执行SQL前调用一句代码即可实现分页,并得到丰富的分页信息。有了这些分页信息,前端可以选用多种分页方法,非常方便!

参考