Spring Boot项目如何使用MyBatis实现分页查询

写在前面:大家好!我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教。我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油,冲鸭!

用知识改变命运,用知识成就未来!加油 (ง •̀o•́)ง (ง •̀o•́)ง

文章目录

为什么需要分页查询

分页查询是一种常见的数据库查询技术,用于将查询结果分成多个页面展示,而不是一次性返回所有数据。使用分页查询主要是为了减少数据库压力、减少网络传输数据量、提高系统的稳定性、提高客户体验

减少数据库压力

一次性查询全部数据(例如百万条记录)会占用大量的资源(CPU、内存、I/O),导致响应变慢甚至系统崩溃。分页后,每次仅查询少量数据(如每页100 条),可以显著降低负载。

减少网络传输数据量

分页查询每次只传输当前页的数据,相比于全表查询会极大的减少网络传输的数据量,降低网络带宽的占用。

提高系统的稳定性

后端服务处理分页查询时,单次处理的数据量可控,避免因一次性加载大数据导致内存耗尽出现 OOM 问题。对于前端也由于无需一次性渲染大量的数据而减少了内存崩溃的风险。

提升用户体验

分页查询由于单次查询的数据量少,后端与前端可以快速的处理相关的数据。用户无需进行长时间的等待,极大的提高了客户的体验。

原始的实现方式

如果不使用分页查询相关的插件需要我们自己计算分页查询的偏移量offset ,还需要手动查询结果集以及数据总条数,并且在相关的 Mapper.xml 中定义分页查询的 SQL 语句。主要实现步骤如下:

计算偏移量

手动分页查询需要我们通过在 SQL 语句中添加分页相关的语法来实现,例如在 MySQL 中的语法:

sql 复制代码
SELECT * FROM users LIMIT #{pageSize} offset #{offset};

其中,#{offset} 表示偏移量,但是前端的分页查询请求中一般只有查询第几页 page 和每页的大小 pageSize。需要我们先计算一下偏移量是多少。

需要注意前端传的 page 是从 0 开始的还是从 1 开始的。

  • 0 开始则 offset = page * pageSize
  • 1 开始则 offset = (page - 1) * pageSize

在Mapper接口中定义查询方法

java 复制代码
/**
 * 分页查询结果集
 * @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语句

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层

java 复制代码
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> pageQuery(DishPageQueryDTO dishPageQueryDTO) {
    log.info("菜品分页查询开始[{}]", dishPageQueryDTO);
    return dishService.pageQuery(dishPageQueryDTO);
}

Service实现类

java 复制代码
@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接口方法

java 复制代码
/**
 * 分页查询结果集
 * @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

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 语句即可。

引入分页插件依赖

xml 复制代码
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

Service实现类

java 复制代码
@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

xml 复制代码
<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语句时也不需要显式地使用 LIMITOFFSET 来实现分页。而且分页插件也会直接将查询的结果集总条数 封装到 Page对象 中,不需要我们手动的查询结果集和总条数。


  1. mysql开启缓存、设置缓存大小、缓存过期机制
  2. PageHelper分页插件最新源码解读及使用
  3. 苍穹外卖
相关推荐
小蒜学长7 小时前
springboot多功能智能手机阅读APP设计与实现(代码+数据库+LW)
java·spring boot·后端·智能手机
追逐时光者8 小时前
精选 4 款开源免费、美观实用的 MAUI UI 组件库,助力轻松构建美观且功能丰富的应用程序!
后端·.net
你的人类朋友8 小时前
【Docker】说说卷挂载与绑定挂载
后端·docker·容器
间彧9 小时前
在高并发场景下,如何平衡QPS和TPS的监控资源消耗?
后端
间彧9 小时前
QPS和TPS的区别,在实际项目中,如何准确测量和监控QPS和TPS?
后端
间彧9 小时前
消息队列(RocketMQ、RabbitMQ、Kafka、ActiveMQ)对比与选型指南
后端·消息队列
brzhang10 小时前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构
brzhang10 小时前
一文说明白为什么现在 AI Agent 都把重点放在上下文工程(context engineering)上?
前端·后端·架构
Roye_ack11 小时前
【项目实战 Day9】springboot + vue 苍穹外卖系统(用户端订单模块 + 商家端订单管理模块 完结)
java·vue.js·spring boot·后端·mybatis
人间有清欢11 小时前
java数据权限过滤
java·mybatis·权限控制·数据过滤