Spring Boot项目如何使用MyBatis实现分页查询及其相关原理
写在前面:大家好!我是
晴空๓
。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭!
用知识改变命运,用知识成就未来!加油 (ง •̀o•́)ง (ง •̀o•́)ง
文章目录
- 为什么需要分页查询
- 减少数据库压力
- 减少网络传输数据量
- 提高系统的稳定性
- 提升用户体验
- 原始的实现方式
- 计算偏移量
- 在Mapper接口中定义查询方法
- 编写SQL语句
- 开发流程及完整代码
- Controller层
- Service实现类
- Mapper接口方法
- xml文件SQL
- 使用插件实现
- 引入分页插件依赖
- Service实现类
- xml文件SQL
- 与原始分页的不同
为什么需要分页查询
分页查询是一种常见的数据库查询技术,用于将查询结果分成多个页面展示,而不是一次性返回所有数据。使用分页查询主要是为了减少数据库压力、减少网络传输数据量、提高系统的稳定性、提高客户体验。
减少数据库压力
一次性查询全部数据(例如百万条记录)会占用大量的资源(CPU、内存、I/O),导致响应变慢甚至系统崩溃。分页后,每次仅查询少量数据(如每页100条),可以显著降低负载。
减少网络传输数据量
分页查询每次只传输当前页的数据,相比于全表查询会极大的减少网络传输的数据量,降低网络带宽的占用。
提高系统的稳定性
后端服务处理分页查询时,单次处理的数据量可控,避免因一次性加载大数据导致内存耗尽出现 OOM 问题。对于前端也由于无需一次性渲染大量的数据而减少了内存崩溃的风险。
提升用户体验
分页查询由于单次查询的数据量少,后端与前端可以快速的处理相关的数据。用户无需进行长时间的等待,极大的提高了客户的体验。
原始的实现方式
如果不使用分页查询相关的插件需要我们自己计算分页查询的偏移量offset,还需要手动查询结果集以及数据总条数,并且在相关的 Mapper.xml 中定义分页查询的 SQL 语句。主要实现步骤如下:
计算偏移量
手动分页查询需要我们通过在 SQL 语句中添加分页相关的语法来实现,例如在 MySQL 中的语法:
SELECT * FROM users LIMIT #{pageSize} offset #{offset};
其中,#{offset} 表示偏移量,但是前端的分页查询请求中一般只有查询第几页 page 和每页的大小 pageSize。需要我们先计算一下偏移量是多少。
需要注意前端传的 page 是从 0 开始的还是从 1 开始的。
- 从 0 开始则 offset = page * pageSize
- 从 1 开始则 offset = (page - 1) * pageSize
在Mapper接口中定义查询方法
/**
* 分页查询结果集
* @param page
* @param offset
* @param name
* @param categoryId
* @param status
* @return
*/
List<DishVO> pageQuery(int page, int offset, String name, Integer categoryId, Integer status);
/**
* 查询总条数
* @return
*/
int getTotalSize();
编写SQL语句
<mapper namespace="com.sky.mapper.DishMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)
values (#{name}, #{categoryId}, #{price}, #{image}, #{description},#{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*, c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%', #{name}, '%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
limit #{page}
offset #{offset}
</select>
<select id="getTotalSize" resultType="java.lang.Integer">
select count(*) from dish;
</select>
</mapper>
开发流程及完整代码
在开发过程中 由外而内 进行开发效率会更高一些,我们一般不需要先写 Mapper.xml 中的 SQL语句,再定义Mapper接口中的方法,然后再通过 Service类 中进行调用。
一般会从 Service类 开始写起,然后再通过编辑器的快捷方式帮助我们生成相关的代码,再一层一层的实现。
Controller层
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO) {
log.info("菜品分页查询开始[{}]", dishPageQueryDTO);
return dishService.pageQuery(dishPageQueryDTO);
}
Service实现类
@Override
public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO) {
// 计算偏移量
int offset = (dishPageQueryDTO.getPage() - 1) * dishPageQueryDTO.getPageSize();
// 查询当前页的数据
List<DishVO> dishVOList = dishMapper.pageQuery(dishPageQueryDTO.getPageSize(), offset, dishPageQueryDTO.getName(),
dishPageQueryDTO.getCategoryId(), dishPageQueryDTO.getStatus());
// 查询数据库中的总条数
int total = dishMapper.getTotalSize();
PageResult pageResult = new PageResult();
pageResult.setTotal(total);
pageResult.setRecords(dishVOList);
log.info("分页查询结果为[{}]", pageResult);
return Result.success(pageResult);
}
Mapper接口方法
/**
* 分页查询结果集
* @param page
* @param offset
* @param name
* @param categoryId
* @param status
* @return
*/
List<DishVO> pageQuery(int page, int offset, String name, Integer categoryId, Integer status);
/**
* 查询总条数
* @return
*/
int getTotalSize();
xml文件SQL
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)
values (#{name}, #{categoryId}, #{price}, #{image}, #{description},#{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})
</insert>
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.*, c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%', #{name}, '%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
limit #{page}
offset #{offset}
</select>
<select id="getTotalSize" resultType="java.lang.Integer">
select count(*) from dish;
</select>
</mapper>
使用插件实现
使用分页插件可以极大的简化分页查询实现。虽然实现的主要原理还是通过原始实现方式中提到的逻辑,但是通过分页插件我们就可以少写很多代码而且一般分页插件(例如 PageHelper) 会通过动态 SQL 的构建和优化,能够有效避免传统分页方法的性能问题。
使用插件进行分页查询只需要修改一下上述原始实现方式的Service类 及 xml 文件中的 SQL 语句即可。
引入分页插件依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
Service实现类
@Override
public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageHelperQuery(dishPageQueryDTO);
PageResult pageResult = new PageResult();
pageResult.setTotal(page.getTotal());
pageResult.setRecords(page.getResult());
log.info("分页查询结果为[{}]", pageResult);
return Result.success(pageResult);
}
xml文件SQL
<select id="pageHelperQuery" resultType="com.sky.vo.DishVO">
select d.*, c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%', #{name}, '%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
与原始分页的不同
使用分页插件进行分页不需要手动计算分页查询的偏移量,在写SQL语句时也不需要显式地使用 LIMIT 和 OFFSET 来实现分页。而且分页插件也会直接将查询的结果集和总条数封装到 Page对象 中,不需要我们手动的查询结果集和总条数。分页插件具体的实现方式之后再单独整理一篇博客。
未完待续……
- mysql开启缓存、设置缓存大小、缓存过期机制
- PageHelper分页插件最新源码解读及使用
- 苍穹外卖