【Spring Boot】基于MyBatis的条件分页
文章目录
- 前言
- 一、写在前面
- 1.1为什么需要分页
- 1.2如何实现分页
- 二、准备工作
- 2.1 分页结果的封装
- 2.2 条件分页查询的封装
- 三、MyBatis的分页实现
- 3.1 手动分页
- 3.2 PageHelper分页
- 3.3 条件分页
- 总结
前言
条件分页是平常工作生产中十分常见的场景。储存过程还流行的年代,喜欢把分页逻辑封装在储存过程里,称之为数据库分页。甚至在前端,我们也可以通过对Array的操作实现前端分页。
本篇文章主要还是在Spring Boot框架上基于MyBatis来实现分页,并且引申出诸如分页数据封装对象,PageHelper辅助工具和MyBatis XML中实现条件语句这些工作中实用的点,希望能帮助到大家。
一、写在前面
1.1为什么需要分页
对于分页而言,本身可以简单理解成为将本应完整执行数据查询结果分批次返回。比方说我们查询最近七日的室内温度历史,这个室内温度在数据库中是每分钟保存一条,那么一次完整的查询需要查询10,080数据。而这个历史详情数据对于前端来说,它是没有必要完全展示出来的,无论是通过表格还是什么其他途径(除非那种跨度很大的趋势折线图)。因为一来作为显示界面,数据展示的区域有限,二来浏览器或者其他GUI界面,大范围的渲染和重绘是非常消耗资源的,势必会造成界面拖拽卡顿。
1.2如何实现分页
试想一下,在实现分页的时候我们需要知道要哪一部分的数据。在查询数据库的时候,我们一般是通过对查询语句指定从第几行开始选择多少行数据来实现分页。像MS SqlServer是通过OFFSET和FETCH关键字(老版本的需要使用ROW_NUMBER () 这种窗口函数)。
SELECT * FROM Student ORDER BY id DESC OFFSET 100 ROWS FETCH NEXT 10 ROWS ONLY;
MySql则是LIMIT 和OFFSET关键字组合(老版本的只用LIMIT ),后面带上开始的行数和要返回的行数
SELECT * FROM Student ORDER BY id DESC LIMIT 10 OFFSET 20;
上面是就数据库层面的讨论,对于service层或者controller层,其实没有必要去过多关心数据在数据库中是如何取得,它更多应该是符合前端得执行逻辑。对于我们前端来说,或者说对于用户来说,他其实也不关心我要从第几行取数据,取多少行。一般典型的分页图形界面会包含总数,当前页,可选页以及每页展示的数据条数。其中涉及到和后端交互的一般就是每页展示的数据条数和要查询的页码。

那么在controller层,它接收到前端传过来的页码数和待展示的条数,service层需要将其转换成数据库理解的开始的行数和要返回的行数。
二、准备工作
2.1 分页结果的封装
前面我们了解到分页是将一个完整的数据查询结果分批次返回,我们思考下,返回的内容肯定是要包含当前页码的数据,另外就是前端也需要后端返回的总条数作为一个参考,在前端得出该在当前展示条数的情况下,分页需要划分为多少个页码。
考虑到后续的维护,我们可以把分页后的结果用一个对象包裹。包含查询的总条数和分页后的内容。
它包含两个属性,分别是count(总条数)和pageData(分页后的内容),其中pageData是List< T>一个泛型集合,满足各种类型。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {private Long count;private List<T> pageData;
}
2.2 条件分页查询的封装
在一次分页查询中,对于controller层,会接收到很多参数,考虑用一个request DTO对象封装是很有必要的。这里pageIndex和pageSize都是必传的分页参数,然后后面包含业务相关的条件参数。
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
public class StudentQueryParam {private Integer pageIndex;private Integer pageSize;private String name;private Integer sex;@DateTimeFormat(pattern = "yyyy-MM-dd")private LocalDate createTime;
}
三、MyBatis的分页实现
以下的代码基于MySql的版本
3.1 手动分页
MySql中是通过LIMIT和OFFSET关键字组合,在老版本中其实只用一个LIMIT关键字就行,比如
SELECT id,NAME,age,sex,class_no,create_time,update_time FROM student ORDER BY update_time LIMIT 10 OFFSET 20;
这样的话我们就需要在MyBatis的XML语句中传入指定的pageIndex和pageSize。
Mapper层 MyBatis
总条数
public Long totalCount();
<mapper namespace="org.araby.restful.mapper.StudentMapper"><select id="totalCount">select count(*) from student</select>
</mapper>
分页数据
List<Student> list(Integer startNum,Integer pageSize);
<mapper namespace="org.araby.restful.mapper.StudentMapper"><select id="list" resultType="org.araby.restful.pojo.Student">SELECT id,NAME,age,sex,class_no,create_time,update_time FROM student ORDER BY update_time limit #{startNum},#{pageSize}</select>
</mapper>
Service层
@Overridepublic PageResult<Student> page(Integer pageIndex, Integer pageSize) {Long totalCount = studentMapper.totalCount();Integer startNum = (pageIndex - 1) * pageSize;List<Student> pageDatas= studentMapper.list(startNum, pageSize);return new PageResult<Student>(totalCount ,pageDatas);}
3.2 PageHelper分页
除了手动去指定页码和条数,MyBatis下还有一个第三方的分页插件,叫PageHelper。它能够帮助我们自动完成分页逻辑。
引入pom坐标
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version></dependency>
Mapper层 MyBatis
使用PageHelper的时候,我们不需要在Mapper层手动指定分页SQL,也不需要传入分页的参数
List<Student> list();
<select id="list" resultType="org.araby.restful.pojo.Student">SELECT id,NAME,age,sex,class_no,create_time,update_time FROM student</select>
Service层
通过pom坐标引入的PageHelper,需要在Service代码里注册下起始页码和查询条数。然后PageHelper插件就会根据这两个参数,拦截到查询的SQL语句,并在里面添加上分页的逻辑SQL。
@Overridepublic PageResult<Student> page(StudentQueryParam queryParam) {PageHelper.startPage(queryParam.getPageIndex(), queryParam.getPageSize());List<Student> empList = studentMapper.list();Page<Student> page = (Page<Student>) empList;return new PageResult<>(page.getTotal(), page.getResult());}

通过观察执行的语句,能清晰的发现PageHelper是在原始语句后面加入了LIMIT ?, ?,然后将分页参数带上去了。
1.值得注意的是由于PageHelper的原理是拦截SQL语句,并且在后面加上LIMIT ?, ?。这种特性导致如果我们手动给语句加一个诸如‘;’的结束符号。那么语句是会语法错误的。
2. PageHelper只对紧跟的第一条 SQL生效,若中间有其他 SQL,则无效
3.3 条件分页
条件分页中,因为包含各种条件,需要用上上面我们封装好的DTO request对象。
List<Student> list(StudentQueryParam queryParam);
这里将条件封装到where节点下,这样就能自动填充where和and关键字
<mapper namespace="org.araby.restful.mapper.StudentMapper"><select id="list" resultType="org.araby.restful.pojo.Student">SELECT id,NAME,age,sex,class_no,create_time,update_time FROM student<where><if test="name != null and name != ''">student.name like CONCAT('%',#{name},'%')</if><if test="sex != null">and student.sex = #{sex}</if><if test="createTime != null">and student.create_time > #{createTime}</if></where></select>
</mapper>
Service层
@Overridepublic PageResult<Student> page(StudentQueryParam queryParam) {PageHelper.startPage(queryParam.getPageIndex(), queryParam.getPageSize());List<Student> empList = studentMapper.list(queryParam);Page<Student> page = (Page<Student>) empList;return new PageResult<>(page.getTotal(), page.getResult());}
Controller层
@GetMapping("/students/page")public Result Page(StudentQueryParam queryParam) {log.info("分页查询:{},{},{},{},{},{}", queryParam.getPageIndex(), queryParam.getPageSize(), queryParam.getName(), queryParam.getSex(), queryParam.getCreateTime());PageResult<Student> page = studentService.page(queryParam);return Result.success(page);}
总结
以上就是在Spring Boot中基于MyBatis 实现分页的方案,覆盖了手动分页、PageHelper和条件分页这些核心场景。
