用于 Mybatis 查询时快速使用分页功能,官方详细文档地址:PageHelper 插件

此处只记录简单使用的配置

使用配置

导入 maven 依赖

1
2
3
4
5
<dependency>
<groupId>com.GitHub.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>RELEASE</version>
</dependency>

在 MyBatis 配置文件中配置拦截器插件

plugins标签在配置文件中的位置必须符合要求,否则会报错,顺序如下

  1. <properties>

  2. <settings>

  3. <typeAliases>

  4. <typeHandlers>

  5. <objectFactory>

  6. <objectWrapperFactory>

  7. <plugins>

  8. <environments>

  9. <databaseIdProvider>

  10. <mappers>

将如下插件标签加入配置文件

1
2
3
4
5
6
7
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>

插件的简单使用

通过MyBatis PageHelper进行分页查询实际上非常简单,只需在 service(或 mapper)方法执行查询前,调用一次 PageHelper.startPage(pageNum,pageSize)来设置分页查询参数即可,其中pageNum为记录页数,pageSize为单页记录数量。此时 service(或 mapper)方法的查询结果就是分页后的结果了。

如果期望获得相关的分页信息,还可以将查询结果封装到PageInfo对象中,以获得总页数、总记录数、当前页数等相关分页信息

例子

该实例用于分页查询一个书籍列表中的书籍信息,创建 Junit 测试来测试分页结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BookQuery {
private static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
private static BookMapper bookMapper = (BookMapper) ctx.getBean("bookMapper");

public List<Book> findPages(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
return bookMapper.selectAll();
}
}

/***
* Junit test for PageHelper
*/
@Test
public void TestPageHelper(){
BookQuery bookQuery=new BookQuery();
List<Book> pages =bookQuery.findPages(1, 2);
for(Book book:pages){ System.out.println("book = " + book); }
}

对于数据库,我们定义一个随机数据集

ISBN书名定价出版日期
9780321928429C Primer Plus, 6th Edition652012-12-20
9787111544937深入理解计算机系统1102016-08-18
9787115317957Machine Learning in Action782013-06-18
9787115478771Deep learning with TensorFlow642018-12-26
9787115490841动手学深度学习582019-06-26

执行 JUnit 单元测试,输出结果如下

1
2
3
4
book = Book{ISBN=9780321928429	书名=C Primer Plus, 6th Edition	价格=65.0	出版日期=2012-12-20}
book = Book{ISBN=9787111544937 书名=深入理解计算机系统 价格=110.0 出版日期=2016-08-18}
book = Book{ISBN=9787115317957 书名=Machine Learning in Action 价格=78.0 出版日期=2013-06-18}
book = Book{ISBN=9787115478771 书名=Deep learning with TensorFlow 价格=64.0 出版日期=2018-12-26}

修改查询时传入的参数,List<Book> pages =bookQuery.findPages(1, 2),即查询第一页,并规定页面大小为 2,输出结果如下

1
2
book = Book{ISBN=9780321928429	书名=C Primer Plus, 6th Edition	价格=65.0	出版日期=2012-12-20}
book = Book{ISBN=9787111544937 书名=深入理解计算机系统 价格=110.0 出版日期=2016-08-18}

PageInfo 类

对于 PageHelper,它的方法使用了静态的ThreadLocal参数,分页参数和线程是绑定的

当我们对一个线程执行分页查询时,我们有的时候想获取它的分页信息,PageHelper 提供了一个更加强大的分页类PageInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BookQuery {
private static ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
private static BookMapper bookMapper = (BookMapper) ctx.getBean("bookMapper");
public PageInfo<Book> findPageInfo(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
return new PageInfo<>(bookMapper.selectAll());
}
}

@Test
public void TestPageInfo() {
BookQuery bookQuery = new BookQuery();
PageInfo<Book> page = bookQuery.findPageInfo(2, 2);
System.out.println("当前页号 = " + page.getPageNum());
System.out.println("页面大小 = " + page.getPageSize());
System.out.println("当前页面开始行号 = " + page.getStartRow());
System.out.println("当前页面结束行号 = " + page.getEndRow());
System.out.println("总记录数量 = " + page.getTotal());
System.out.println("总页号 = " + page.getPages());
System.out.println("获取第一页 = " + page.getNavigateFirstPage());
System.out.println("获取最后一页 = " + page.getNavigateLastPage());
System.out.println("当前是否为第一页? : " + page.isIsFirstPage());
System.out.println("当前是否为最后一页? : " + page.isIsLastPage());
System.out.println("是否有上一页? : " + page.isHasPreviousPage());
System.out.println("是否有下一页? : " + page.isHasNextPage());
}

对上述代码的操作,依然使用测试数据集,结果输出如下

1
2
3
4
5
6
7
8
9
10
11
12
当前页号 = 2
页面大小 = 2
当前页面开始行号 = 3
当前页面结束行号 = 4
总记录数量 = 5
总页号 = 3
获取第一页 = 1
获取最后一页 = 3
当前是否为第一页? : false
当前是否为最后一页? : false
是否有上一页? : true
是否有下一页? : true

保证方法的安全性

PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

只要可以保证在PageHelper方法调用后紧跟MyBatis查询方法,这就是安全的。因为PageHelperfinally代码段中自动清除了ThreadLocal存储的对象

如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到MappedStatement时),这种情况由于线程不可用,也不会导致 ThreadLocal参数被错误的使用。

但是如果代码形式如下,就是不安全的用法

1
2
3
4
5
6
7
PageHelper.startPage(1, 10);
List<Book> list;
if(param1 != null){
list = BookMapper.selectIf(param1);
} else {
list = new ArrayList<Book>();
}

这种情况下由于param1存在null的情况,若 Mybatis 的查询语句没有被查询,这就会导致PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

如果上述样例改写成如下的形式,就能保证安全

1
2
3
4
5
6
7
List<Book> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = BookMapper.selectIf(param1);
} else {
list = new ArrayList<Book>();
}

这种写法就能保证安全。

如果你对此不放心,你可以手动清理ThreadLocal存储的分页参数,可以像下面这样使用:

1
2
3
4
5
6
7
8
9
10
11
List<Book> list;
if(param1 != null){
PageHelper.startPage(1, 10);
try{
list = BookMapper.selectIf(param1);
} finally {
PageHelper.clearPage();
}
} else {
list = new ArrayList<Book>();
}

不推荐使用该写法,推荐将分页方法与 Mybatis 绑定执行,这样便能保证用法安全