1.需求
分页查询在业务中很普遍,前端给的参数通常有两个:
当前页数page
(从1开始,即第几页);
每页大小pageSize
基于sql实现,需要自己写select * from xx limit offset, nums;
表示查询从index为offset
的记录开始,的往后nums
条记录,注意offset是从0开始的。
sql所需要的两个参数,与前端传参是不匹配的,存在一个转换公式:
o
f
f
s
e
t
=
(
p
a
g
e
−
1
)
×
p
a
g
e
S
i
z
e
offset = (page-1)\times pageSize
offset=(page−1)×pageSize
n
u
m
s
=
p
a
g
e
S
i
z
e
nums=pageSize
nums=pageSize
当然可以自己做如上转化,但麻烦,有没有更快捷的方式呢?
2.思路
使用MyBatis分页插件PageHelper,接收前端传参后自动转换
3.解决方案
引入插件:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
使用模板(service层的分页查询函数):
//员工分页查询
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
//开始分页
PageHelper.startPage(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
//返回类型是Page,泛型为单条记录的实体类
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
//service层给controller层返回的数据封装为PageResult
long total = page.getTotal();//总记录数
List<Employee> records = page.getResult();//记录列表
return new PageResult(total, records);
}
这里我用的是xml,方便写动态sql,注意:sql语句中不要自己再写limit了!! PageHelper会自动拼接limit相关语句
<mapper namespace="com.sky.mapper.EmployeeMapper">
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != ''">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
</mapper>
分析:
1.拿的参数就是前端给的page
页码,和pageSize
每页数量
2.PageHelper.startPage
开始分页
3.调用mapper查询,返回类型是Page<单条记录的实体类>,这玩意其实就是个List
4.返回给controller有两部分内容:总记录数,和查询到的记录。为什么要有总记录数? 因为前端要根据总记录数计算总共有多少页呀。
4.底层实现,两个问题
4.1 PageHelper.startPage
怎么对后续查询起作用的
代码里面,似乎没有看到后续过程中调用PageHelper
,怎么生效的呢?
查看startPage
源码,经过几个重载后:
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page<E>(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
实例化了一个page对象,包含了pageNum和pageSize两个关键的分页参数,Page类继承ArrayList。
倒数第三行:setLocalPage(page);
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
LOCAL_PAGE又是啥?
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
LOCAL_PAGE是个ThreadLocal对象,即将page作为当前线程变量。
以下只是我的简单理解,具体调用我也没搞明白…
page对象成为当前线程变量后,在“需要的时候”取出来,做两件事情:
1.自动计算sql需要的offset和nums。
2.在自定义的sql语句中,拼接limit以及参数
4.2 总记录数怎么获取的
仍然是简单理解,没定位到源码部分
在执行4.1的拼接limit后的sql语句之前,会自动先执行select count(0) from xxx
获取总记录数
注意:如果查询带条件where,count(0)查询总数时也是带条件的。
这个总数,指的是满足你查询条件的总记录数,而非表中所有记录数量
暂且记录到此,以后有机会再去研究底层实现,先记录PageHelper的使用方式。