分页功能
功能配置
在上面的04.数据库配置模块中我们已经在qk-management中加入了Mybatisplus和对应分页的pom.xml文件配置,现在我们需要在qk-management模块中添加分页插件的配置。

配置的代码如下:
package com.cghbh.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件(3.5.x 最新版写法)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 1) 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 2) 添加分页拦截器(指定数据库类型,性能更好)
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}封装PageResult
对应分页查询来说,无论是哪个业务模块的分页查询,给前端响应的数据格式都是固定的,包含两项信息:total 表示总记录数,rows 表示结果列表。
那为了方便于数据封装,我们可以定义一个分页结果的封装类 PageResult,将其存放在 qk-common 模块的 com.cghbh.common 包下,代码如下:
package com.cghbh.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分页结果封装类
* 用于封装分页查询的结果,包含总记录数和当前页的数据列表
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
/*
我们定义类的时候也可以定义泛型, 让使用者来确定, 该类可以用于部门分页, 用户分页...等等 , 提供类的通用性
*/
public class PageResult<T> {
/**
* 总记录数
*/
private long total;
/**
* 当前页的数据列表
*/
// 这里的T是泛型,表示当前页的数据列表的类型, 一般就是查询表对应的实体类
private List<T> rows;
}使用测试
6.3.1.DeptController代码
package com.cghbh.controller;
import com.cghbh.common.PageResult;
import com.cghbh.common.Result;
import com.cghbh.entity.Dept;
import com.cghbh.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/depts")
public Result getDeptListByPage(
// 注意:如果加上了RequestParam注解,如果没加required=false表示强制必须传递的参数,不传会报错
String name,
Integer status,
// RequestParam的用法更加灵活,可以配置query参数字段映射,设置默认值和是否必须传递
// 如果加的是RequestParam注解,如果没有加required默认是true,这个时候不加默认值会报错
@RequestParam(value = "page", required = true, defaultValue = "1") Integer page,
@RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize
) {
PageResult<Dept> deptPageResult = deptService.getDeptListByPage(name, status, page, pageSize);
return Result.success(deptPageResult);
}
}6.3.2.DeptService代码
package com.cghbh.service;
import com.cghbh.common.PageResult;
import com.cghbh.entity.Dept;
public interface DeptService {
PageResult<Dept> getDeptListByPage(String name, Integer status, Integer page, Integer pageSize);
}6.3.3.DeptServiceImpl代码
package com.cghbh.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cghbh.common.PageResult;
import com.cghbh.entity.Dept;
import com.cghbh.mapper.DeptMapper;
import com.cghbh.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Override
public PageResult<Dept> getDeptListByPage(String name, Integer status, Integer page, Integer pageSize) {
LambdaQueryWrapper<Dept> queryWrapper = Wrappers.lambdaQuery(Dept.class)
// 操作之前需要先判断是否为空值,如果是String类型推荐用StringUtils.isNotBlank来判断是否为null或者''
// 如果是非String类型的判断是否为null即可,推荐对应int、double这些数据都使用包装类型如Integer、Double进行声明
.eq(status != null, Dept::getStatus, status)
.like(StringUtils.isNotBlank(name), Dept::getName, name);
Page<Dept> deptPageInfo = deptMapper.selectPage(new Page<>(page, pageSize), queryWrapper);
PageResult<Dept> deptPageResult = new PageResult<>();
// 注意下面的两个方法是跟PageResult类牢牢绑定的,如果PageResult的名称改变了,那这里的调用方法也需要调整!!!
deptPageResult.setTotal(deptPageInfo.getTotal());
deptPageResult.setRows(deptPageInfo.getRecords());
return deptPageResult;
}
}配合xml的自定义分页查询
自定义分页
现在由于是多表查询,而mybatis-plus仅仅适合的单表的相关操作, 所以我们必须手动编写多表的sql语句, 但是分页还是希望mybatis-plus帮我们来实现, 如何实现呢?
我们仅仅只需要将Page这个对象以参数的形式传入给Mapper中的自定义方法, 并且将Page类型返回
mybatis-plus就会在我们自定义sql语句加上分页的逻辑, 并且按照我们自定义sql的条件查询数据的total然后将当前页的分页数据和total存放到Page对象, 我们只需要从Page中取对应的内容即可
✅ 关键点: 当 Mapper 方法第一个参数是 Page,xml 里面写正常查询 SQL(不要自己写 limit),MP 分页插件会自动拦截 SQL,自动拼接 LIMIT,并且自动执行 count 查询填充 total!
IPage<T>:接口,定义分页规范(当前页、每页条数、总条数、记录列表等方法)
Page<T>:实现类,MyBatis-Plus 提供的默认实现,实现了 IPage<T>
实体类
package com.cghbh.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
private Integer id;
private String username;
// 转JSON的时候忽略这个字段
@JsonIgnore
private String password;
private String name;
private String phone;
private String email;
private Integer gender;
private Integer status;
private Integer deptId;
private Integer roleId;
private String image;
private String remark;
// 加密盐值
@JsonIgnore
private String salt;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 部门名称
* 在数据库中排除这两个字段,但是实际返回的时候需要使用
*/
@TableField(exist = false)
private String deptName;
/**
* 角色名称
* 在数据库中排除这两个字段,但是实际返回的时候需要使用
*/
@TableField(exist = false)
private String roleName;
}Mapper类型
✅ 第一个参数必须是 IPage,第二个是查询条件 DTO。
package com.cghbh.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cghbh.dto.UserPageDto;
import com.cghbh.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
/**
* 自定义xml分页查询
* @param page 分页对象(必须第一个参数)
* @param userPageDto 查询条件
* @return Page<User>
*/
// 注意:里面是IPage,外面是Page,Page是IPage的实现类
Page<User> getUsersByPage(IPage<User> page, @Param("userPageDto") UserPageDto userPageDto);
}查询条件 DTO
package com.cghbh.dto;
import lombok.Data;
@Data
public class UserPageDto {
private String name;
private Integer status;
private String phone;
private Integer deptId;
// 作为默认值
private Integer page = 1;
// 作为默认值
private Integer pageSize = 2;
}UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 引入mybatis的xml方式编写sql的约束 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cghbh.mapper.UserMapper">
<select id="getUsersByPage" resultType="com.cghbh.entity.User">
select u.*, d.name dept_name, r.name role_name
from user u left join dept d on u.dept_id = d.id
left join role r on u.role_id = r.id
<where>
<if test="userPageDto.name !='' and userPageDto.name !=null">
and u.name like concat('%', #{userPageDto.name}, '%')
</if>
<if test="userPageDto.phone != '' and userPageDto.phone != null">
and u.phone like concat('%', #{userPageDto.phone}, '%')
</if>
<if test="userPageDto.deptId !=null">
and u.dept_id = #{userPageDto.deptId}
</if>
<if test="userPageDto.status !=null">
and u.status = #{userPageDto.status}
</if>
</where>
order by u.update_time desc
</select>
</mapper>原理:分页插件拦截这条 select 语句,自动生成两条 SQL
- count sql:
SELECT COUNT(*) FROM t_user where ...填充 total - 分页 sql:
SELECT id,name... FROM t_user where ... LIMIT ?,?
请注意 SQL语句编写
- 在编写sql的时候,由于参数是两个对象, 我们在获取参数的时候, 必须通过标注对象名
- 当多表查询的时候, 写条件的时候如果条件涉及的列在多张表中存在, 需要加上表名的限制, 否则会报Ambiguous...相关的错误, 这个错误的意思是多张表出现相同的字段, 模棱两可了, 不知道指的是那张表的这个字段了
Service层调用示例
package com.cghbh.service.impl;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cghbh.common.PageResult;
import com.cghbh.dto.UserPageDto;
import com.cghbh.entity.User;
import com.cghbh.exception.NotFoundException;
import com.cghbh.mapper.UserMapper;
import com.cghbh.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
@Slf4j
@RequiredArgsConstructor
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
private final UserMapper userMapper;
@Override
public PageResult<User> getUsersByPage(UserPageDto userPageDto) {
IPage<User> page = new Page<>(userPageDto.getPage(), userPageDto.getPageSize());
Page<User> pageResult = userMapper.getUsersByPage(page, userPageDto);
return new PageResult<>(pageResult.getTotal(), pageResult.getRecords());
}
}Controller示例
package com.cghbh.controller;
import com.cghbh.common.PageResult;
import com.cghbh.common.Result;
import com.cghbh.dto.UserPageDto;
import com.cghbh.entity.User;
import com.cghbh.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/users")
public class UserController {
private final UserService userService;
/**
* 根据分页查询用户
*
* @param userPageDto 分页数据
* @return 用户数据列表
*/
@GetMapping
public Result getUsersByPage(UserPageDto userPageDto) {
PageResult<User> userPageResult = userService.getUsersByPage(userPageDto);
return Result.success(userPageResult);
}
}自定义分页总结
MP 分页依靠 PaginationInnerInterceptor 插件拦截 SQL 实现;
简单查询用 mapper.selectPage;
复杂 SQL 写 XML,Mapper 方法第一个参数传 IPage,xml 只写业务查询不加 limit,插件自动分页、统计总数;