Spring脚手架集成分页插件
# 前言
在Spring脚手架创建简记 (opens new window)中,我们搭建了一套比较顺手的脚手架,考虑到我们后续的查询逻辑可能会更加复杂,而且可能前端接口还会涉及分页查询,所以我们这里不妨集成一下分页插件,从而实现一行代码实现分页查询。
# 集成步骤
# 引入分页插件依赖
如下所示,我们到pom文件中引入如下插件。
<!-- pagehelper 插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.13</version>
</dependency>
2
3
4
5
6
# 编写测试代码
还记得我们之前文章中用到的demo的crud的代码吗?我们不妨接着list接口进行测试,首先我们到demo表创建30条数据。可以看到我们数据库的数据如下图所示:

我们在代码中添加一行 PageHelper.startPage(1,10);这样一来,分页插件就会查询第一页的10条数据了。
@GetMapping("/demo/list")
public List<DemoResp> list(DemoReq req) {
PageHelper.startPage(1,10);
DemoExample demoExample = new DemoExample();
DemoExample.Criteria criteria = demoExample.createCriteria();
if (StrUtil.isNotEmpty(req.getName())){
criteria.andNameLike("%"+req.getName()+"%");
}
List<Demo> demoList = demoMapper.selectByExample(demoExample);
List<DemoResp> resultList = CopyUtil.copyList(demoList, DemoResp.class);
return resultList;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
查看我们的查询结果,确实是10条数据。

这一点我们同样可以在系统的控制台中得以印证,如下所示可以看出分页组件会先去查当前数据库有几条数据,然后使用limit进行分页查询。

同时分页插件,还支持查询当前分页的信息情况,代码如下图所示,可以看到我们可以通过PageInfo获取到分页的详细信息。
@GetMapping("/demo/list")
public List<DemoResp> list(DemoReq req) {
DemoExample demoExample = new DemoExample();
DemoExample.Criteria criteria = demoExample.createCriteria();
if (StrUtil.isNotEmpty(req.getName())){
criteria.andNameLike("%"+req.getName()+"%");
}
PageHelper.startPage(2,10);
List<Demo> demoList = demoMapper.selectByExample(demoExample);
List<DemoResp> resultList = CopyUtil.copyList(demoList, DemoResp.class);
//通过PageInfo获取当前查询的分页信息
PageInfo<DemoResp> pageInfo=new PageInfo<>(resultList);
LOG.info("当前分页查询结果返回行数:{},当前页数:{}",pageInfo.getTotal(),pageInfo.getPages());
return resultList;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
输出结果如下,功能是不是很强大呢?

# 优化代码
了解了基本的使用之后我们不妨来对功能进行优化,先来看看我们现有代码,可以看到我们对代码没有对分页查询请求查询进行统一封装和分页的响应结果的统一抽取。

所以我们现在不妨进行一下优化,首先定义一下统一的分页查询请求的分页查询参数类PageReq,可以看到我们将类放到req目录下。

代码如下,可以看到这个请求参数包含用户需要查询的页码和查询总数。
public class PageReq {
private int page;
private int size;
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("PageReq{");
sb.append("page=").append(page);
sb.append(", size=").append(size);
sb.append('}');
return sb.toString();
}
}
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
27
28
29
30
31
32
完成之后,我们让原有的demoReq继承这个类,使之拥有分页查询的参数。

同理我们的resp文件夹,定义一个分页查询结果响应类,代码如下,可以看到我们该类会将用户的分页查询结果列表存到list,并且还可以记录分页总数。
public class PageResp<T> {
private long total;
private List<T> list;
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("PageResp{");
sb.append("total=").append(total);
sb.append(", list=").append(list);
sb.append('}');
return sb.toString();
}
}
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
27
28
29
30
然后我们的代码就可以改造了,先来改造service层代码
@GetMapping("/demo/list")
public PageResp<DemoResp> list(DemoReq req) {
DemoExample demoExample = new DemoExample();
DemoExample.Criteria criteria = demoExample.createCriteria();
if (StrUtil.isNotEmpty(req.getName())){
criteria.andNameLike("%"+req.getName()+"%");
}
//分页查询参数
PageHelper.startPage(req.getPage(),req.getSize());
List<Demo> demoList = demoMapper.selectByExample(demoExample);
List<DemoResp> resultList = CopyUtil.copyList(demoList, DemoResp.class);
//通过PageInfo获取当前查询的分页信息
PageInfo<DemoResp> pageInfo=new PageInfo<>(resultList);
LOG.info("当前分页查询结果返回行数:{},当前页数:{}",pageInfo.getTotal(),pageInfo.getPages());
//封装统一查询结果
PageResp<DemoResp> pageResp=new PageResp<>();
pageResp.setList(resultList);
pageResp.setTotal(pageInfo.getTotal());
return pageResp;
}
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
27
28
29
由于返回值的改变,我们的controller层也同步修改一下
@GetMapping("/list")
public CommonResp<PageResp<DemoResp>> list(DemoReq req) {
CommonResp<PageResp<DemoResp>> resp=new CommonResp<>();
resp.setContent(demoService.list(req));
return resp;
}
2
3
4
5
6
我们的请求如下

最终响应结果的格式如下,自此我们的分页查询功能集成完成了。
{
"success": true,
"content": {
"total": 5,
"list": [
{
"id": 1,
"name": "测试"
},
{
"id": 2,
"name": "测试"
},
{
"id": 3,
"name": "测试"
},
{
"id": 4,
"name": "测试"
},
{
"id": 5,
"name": "测试"
}
]
}
}
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
27
28
# 工作原理
很多都好奇分页插件是如何实现对mybatis的查询加入分页功能的,我们不妨看看mybatis查询日志时的sql语句。
可以看到sql查询出现了limit语句,我们不妨到源码中查看它是如何实现的。

所以我们在查询语句中打个断点

经过笔者的断点调试,我们从堆栈中看到这样一道轨迹,可以看出调用mybatis查询之后会一路走到pageHelper的代理。

首先该代理会查询本次分页查询对应表的总行数。

然后分页插件的代理会拦截到mybatis的请求语句,执行器等参数。

然后因为我们用的是MySQL,所以代理会走到MySqlDialect将拦截到sql语句拼接一个limit交给mybaits继续执行,从而实现分页查询。

# 注意事项
笔者在开发的时候注意到分页查询仅仅对第一次查询的语句做到拦截,后续的语句不会进行分页拦截,这一点读者开发时需要注意一下。
