Mybatis Plus 分页查询数据(图解)

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

截止目前, 星球 内专栏累计输出 72w+ 字,讲解图 3103+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2400+ 小伙伴加入学习 ,欢迎点击围观

大家好,我是小哈。

本小节中,我们将学习如何通过 Mybatis Plus 分页查询数据库表中的数据。

什么是分页查询?

下图是小哈从京东上查询关键词 「手机」 ,展示的手机数据就是分页查询,共有 91 页:

京东商城的分页查询京东商城的分页查询

分页查询就是把需要查询的数据集进行分批展示,比如商品表中有 1万 条手机数据,每页按固定数量展示。

为什么需要分页查询?

  1. 前端页面能够展示的内容有限;

  2. 当数据库中数据量太多,比如 100W 条,一次性全部返回,查询速度慢,而且内存也顶不住;

表结构

了解到分页查询相关概念后,我们来上手 Mybatis Plus 的分页查询功能,还是之前小节中定义好的用户测试表, 执行脚本如下:

DROP TABLE IF EXISTS user;

CREATE TABLE `user` (
  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(2) NOT NULL DEFAULT 0 COMMENT '性别,0:女 1:男',
  PRIMARY KEY (`id`)
) COMMENT = '用户表';

定义实体类

定义一个名为 User 实体类:

@TableName("user")
public class User {
    /**
     * 主键 ID, @TableId 注解定义字段为表的主键,type 表示主键类型,IdType.AUTO 表示随着数据库 ID 自增
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private Integer age;
    /**
     * 性别
     */
    private Integer gender;
}

不明白 Mybatis Plus 实体类注解的小伙伴,可参考前面小节 , 有详细解释。

新增测试数据

分页查询前,先通过代码插入一些测试数据,执行代码如下:

// 循环插入 100 条测试数据
for (int i = 0; i < 100; i++) {
    User user = new User();
    user.setName("犬小哈" + i);
    user.setAge(i);
    user.setGender(1);

    userMapper.insert(user);
}

TIP : 不清楚如何插入数据可翻阅前面 《新增数据》 小节;

添加分页插件

接着,在 MybatisPlusConfig 配置类中,添加分页插件 PaginationInnerInterceptor:

/**
 * @Author: 犬小哈
 * @From: 公众号:小哈学Java, 网站:www.quanxiaoha.com
 * @Date: 2022-12-15 18:29
 * @Version: v1.0.0
 * @Description: TODO
 **/
@Configuration
@MapperScan("com.quanxiaoha.mybatisplusdemo.mapper")
public class MybatisPlusConfig {

    /**
     * 分页插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
  
}

开始分页查询数据

Mybatis Plus 对 Mapper 层和 Service 层都将常见的增删改查操作封装好了,只需简单的继承,即可轻松搞定对数据的增删改查,本文重点讲解分页查询相关的部分。

Mapper 层

定义一个 UserMapper , 让其继承自 BaseMapper :

public interface UserMapper extends BaseMapper<User> {
}

然后,注入 Mapper :

@Autowired
private UserMapper userMapper;

BaseMapper 提供的分页查询相关的方法如下:

解释一下每个方法的作用:

// 分页查询,page 用于设置需要查询的页数,以及每页展示数据量,wrapper 用于组装查询条件
IPage<T> selectPage(IPage<T> page, Wrapper<T> queryWrapper);
// 同上,区别是用 map 来接受查询的数据
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, Wrapper<T> queryWrapper);

参数说明:

类型参数名描述
Wrapper<T>queryWrapper实体对象封装操作类(可以为 null
IPage<T>page分页查询条件(可以为 RowBounds.DEFAULT

示例代码

接下来,小哈来演示一些示例代码以便你快速了解如何使用分页查询:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 查询第 2 页数据,每页 10 条
Page<User> page = new Page<>(2, 10);

page = userMapper.selectPage(page, queryWrapper);
System.out.println("总记录数:" + page.getTotal());
System.out.println("总共多少页:" + page.getPages());
System.out.println("当前页码:" + page.getCurrent());
// 当前页数据
List<User> users = page.getRecords();

执行上面的代码,实际上执行了两条 SQL : 先执行 COUNT(*) 查询出记录总数,然后才是分页语句 LIMIT:

Page 类说明

该类继承了 IPage 类,实现了 简单分页模型 ,如果你要实现自己的分页模型可以继承 Page 类或者实现 IPage

属性名类型默认值描述
recordsListemptyList查询数据列表
totalLong0查询列表总记录数
sizeLong10每页显示条数,默认 10
currentLong1当前页
ordersListemptyList排序字段信息,允许前端传入的时候,注意 SQL 注入问题,可以使用 SqlInjectionUtils.check(...) 检查文本
optimizeCountSqlbooleantrue自动优化 COUNT SQL 如果遇到 jSqlParser 无法解析情况,设置该参数为 false
optimizeJoinOfCountSqlbooleantrue自动优化 COUNT SQL 是否把 join 查询部分移除
searchCountbooleantrue是否进行 count 查询,如果指向查询到列表不要查询总记录数,设置该参数为 false
maxLimitLong 单页分页条数限制
countIdString xml 自定义 count 查询的 statementId

Service 层

Mybatis Plus 同样也封装了通用的 Service 层 CRUD 操作,并且提供了更丰富的方法。接下来,我们上手看 Service 层的代码结构,如下图:

定义 Service 层定义 Service 层

先定义 UserService 接口 ,让其继承自 IService:

public interface UserService extends IService<User> {
}

再定义实现类 UserServiceImpl,让其继承自 ServiceImpl, 同时实现 UserService 接口,这样就可以让 UserService 拥有了基础通用的 CRUD 功能,当然,实际开发中,业务会更加复杂,就需要向 IService 接口自定义方法并实现:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

注入 UserService :

@Autowired
private UserService userService;

Service 层封装的分页相关方法如下:

// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

示例代码

Service 层的分页方法入参和 Mapper 差不多:

// 组装查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// where age = 30
queryWrapper.eq("age", 30);

// 查询第 2 页数据,每页 10 条
Page<User> page = new Page<>(2, 10);

page = userService.page(page, queryWrapper);
System.out.println("总记录数:" + page.getTotal());
System.out.println("总共多少页:" + page.getPages());
System.out.println("当前页码:" + page.getCurrent());
// 当前页数据
List<User> users = page.getRecords();