跳到主要内容

分页查询

分页是 Web 应用中最常见的功能之一。MyBatis Plus 提供了强大的分页插件,支持多种数据库,使用简单方便。

分页插件配置

Spring Boot 配置

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 {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(500L);
paginationInterceptor.setOverflow(false);

interceptor.addInnerInterceptor(paginationInterceptor);

return interceptor;
}
}

配置参数说明

参数说明默认值
dbType数据库类型MYSQL
maxLimit单页最大限制数量无限制
overflow溢出总页数后是否进行处理false
optimizeType分页优化类型default

支持的数据库类型

DbType.MYSQL        // MySQL
DbType.ORACLE // Oracle
DbType.POSTGRE_SQL // PostgreSQL
DbType.SQL_SERVER // SQL Server
DbType.SQLITE // SQLite
DbType.MARIADB // MariaDB
DbType.H2 // H2

基本分页查询

使用 Page 对象

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class PaginationTest {

@Autowired
private UserMapper userMapper;

@Test
void testPage() {
Page<User> page = new Page<>(1, 10);

Page<User> result = userMapper.selectPage(page, null);

System.out.println("当前页码:" + result.getCurrent());
System.out.println("每页大小:" + result.getSize());
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("是否有上一页:" + result.hasPrevious());
System.out.println("是否有下一页:" + result.hasNext());

List<User> records = result.getRecords();
records.forEach(System.out::println);
}
}

带条件分页

@Test
void testPageWithCondition() {
Page<User> page = new Page<>(1, 10);

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20)
.orderByDesc(User::getCreateTime);

Page<User> result = userMapper.selectPage(page, wrapper);

System.out.println("符合条件的总记录数:" + result.getTotal());
result.getRecords().forEach(System.out::println);
}

Lambda 方式

@Test
void testLambdaPage() {
Page<User> page = new Page<>(1, 10);

userMapper.selectPage(page,
new LambdaQueryWrapper<User>()
.like(User::getName, "张")
.gt(User::getAge, 18)
);

page.getRecords().forEach(System.out::println);
}

Page 对象详解

构造方法

Page<T> page = new Page<>();

Page<T> page = new Page<>(1, 10);

Page<T> page = new Page<>(1, 10, 100);

Page<T> page = new Page<>(1, 10, 100, true);

Page<T> page = new Page<>(1, 10, true);

常用属性

public class Page<T> {
private List<T> records; // 数据列表
private long total; // 总记录数
private long size; // 每页大小
private long current; // 当前页码
private List<OrderItem> orders; // 排序字段
private boolean optimizeCountSql; // 是否优化 COUNT SQL
private boolean searchCount; // 是否查询总数
private boolean optimizeJoinOfCountSql; // 是否优化 JOIN 查询
private Long maxLimit; // 最大单页限制
private long pages; // 总页数
}

常用方法

Page<User> page = new Page<>(1, 10);

page.getRecords();
page.setRecords(List<User> records);
page.getTotal();
page.setTotal(long total);
page.getSize();
page.setSize(long size);
page.getCurrent();
page.setCurrent(long current);
page.getPages();
page.hasPrevious();
page.hasNext();
page.convert(user -> userDTO);

Service 层分页

基本使用

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

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

// 使用
@SpringBootTest
class ServicePageTest {

@Autowired
private UserService userService;

@Test
void testServicePage() {
Page<User> page = new Page<>(1, 10);

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 20);

Page<User> result = userService.page(page, wrapper);

result.getRecords().forEach(System.out::println);
}
}

封装分页方法

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

public IPage<User> searchUsers(UserQuery query, int pageNum, int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.eq(query.getStatus() != null, User::getStatus, query.getStatus())
.between(query.getStartTime() != null && query.getEndTime() != null,
User::getCreateTime, query.getStartTime(), query.getEndTime())
.orderByDesc(User::getCreateTime);

return this.page(page, wrapper);
}
}

自定义分页

Mapper 接口定义

@Mapper
public interface UserMapper extends BaseMapper<User> {

IPage<UserVO> selectUserVOPage(Page<?> page, @Param("name") String name);
}

XML 实现

<select id="selectUserVOPage" resultType="com.example.entity.UserVO">
SELECT
u.id,
u.name,
u.age,
u.email,
d.dept_name as deptName
FROM t_user u
LEFT JOIN t_dept d ON u.dept_id = d.id
WHERE u.deleted = 0
<if test="name != null and name != ''">
AND u.name LIKE CONCAT('%', #{name}, '%')
</if>
ORDER BY u.create_time DESC
</select>

使用示例

@Test
void testCustomPage() {
Page<UserVO> page = new Page<>(1, 10);

IPage<UserVO> result = userMapper.selectUserVOPage(page, "张");

System.out.println("总数:" + result.getTotal());
result.getRecords().forEach(System.out::println);
}

多表关联分页

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

IPage<OrderVO> selectOrderWithUser(Page<?> page, @Param("userId") Long userId);
}
<select id="selectOrderWithUser" resultType="com.example.entity.OrderVO">
SELECT
o.id,
o.order_no,
o.amount,
o.status,
u.name as userName,
u.email as userEmail
FROM t_order o
INNER JOIN t_user u ON o.user_id = u.id
WHERE o.deleted = 0
<if test="userId != null">
AND o.user_id = #{userId}
</if>
ORDER BY o.create_time DESC
</select>

分页结果处理

转换为 VO

@Test
void testConvertToVO() {
Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectPage(page, null);

Page<UserVO> voPage = result.convert(user -> {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
vo.setAgeDesc(user.getAge() + "岁");
return vo;
});

voPage.getRecords().forEach(System.out::println);
}

返回统一格式

@Data
public class PageResult<T> {
private List<T> list;
private long total;
private long pageNum;
private long pageSize;
private long pages;

public static <T> PageResult<T> of(IPage<T> page) {
PageResult<T> result = new PageResult<>();
result.setList(page.getRecords());
result.setTotal(page.getTotal());
result.setPageNum(page.getCurrent());
result.setPageSize(page.getSize());
result.setPages(page.getPages());
return result;
}
}

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
private UserService userService;

@GetMapping("/page")
public PageResult<User> page(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
Page<User> result = userService.page(page);
return PageResult.of(result);
}
}

排序处理

Page 对象排序

@Test
void testPageWithOrder() {
Page<User> page = new Page<>(1, 10);
page.addOrder(OrderItem.desc("create_time"));
page.addOrder(OrderItem.asc("age"));

Page<User> result = userMapper.selectPage(page, null);
}

@Test
void testPageWithOrders() {
Page<User> page = new Page<>(1, 10);
page.setOrders(Arrays.asList(
OrderItem.desc("create_time"),
OrderItem.asc("age")
));

Page<User> result = userMapper.selectPage(page, null);
}

动态排序

@GetMapping("/page")
public PageResult<User> page(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String sortField,
@RequestParam(defaultValue = "desc") String sortOrder) {

Page<User> page = new Page<>(pageNum, pageSize);

if (StringUtils.isNotBlank(sortField)) {
OrderItem orderItem = "asc".equalsIgnoreCase(sortOrder)
? OrderItem.asc(sortField)
: OrderItem.desc(sortField);
page.addOrder(orderItem);
}

Page<User> result = userService.page(page);
return PageResult.of(result);
}

性能优化

不查询总数

当不需要总数时,可以关闭 count 查询提高性能:

@Test
void testPageWithoutCount() {
Page<User> page = new Page<>(1, 10, false);

Page<User> result = userMapper.selectPage(page, null);

System.out.println("总数:" + result.getTotal());
}

优化 COUNT 查询

@Test
void testOptimizeCount() {
Page<User> page = new Page<>(1, 10);
page.setOptimizeCountSql(true);

Page<User> result = userMapper.selectPage(page,
new LambdaQueryWrapper<User>()
.like(User::getName, "张")
.gt(User::getAge, 20)
);
}

设置最大单页限制

@Configuration
public class MybatisPlusConfig {

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInterceptor.setMaxLimit(100L);

interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}

分页常见问题

问题 1:分页不生效

症状:返回全部数据,没有分页

解决方案

  • 检查是否配置了分页插件
  • 确保配置类被 Spring 扫描到
  • 检查 @MapperScan 配置

问题 2:COUNT 查询慢

症状:分页查询总数很慢

解决方案

  • 设置 optimizeCountSql = true
  • 使用 setSearchCount(false) 不查询总数
  • 优化数据库索引

问题 3:内存溢出

症状:大数据量分页时内存溢出

解决方案

  • 使用 setMaxLimit 限制单页最大数量
  • 使用游标分页(Cursor-based Pagination)

小结

本章我们学习了:

  1. 分页插件配置:添加 PaginationInnerInterceptor
  2. 基本分页查询:使用 Page 对象
  3. 条件分页:结合 Wrapper 使用
  4. 自定义分页:Mapper 接口自定义分页方法
  5. 分页结果处理:转换 VO、统一返回格式
  6. 排序处理:Page 对象排序、动态排序
  7. 性能优化:不查询总数、优化 COUNT 查询

参考资源