分页查询
分页是 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)
小结
本章我们学习了:
- 分页插件配置:添加 PaginationInnerInterceptor
- 基本分页查询:使用 Page 对象
- 条件分页:结合 Wrapper 使用
- 自定义分页:Mapper 接口自定义分页方法
- 分页结果处理:转换 VO、统一返回格式
- 排序处理:Page 对象排序、动态排序
- 性能优化:不查询总数、优化 COUNT 查询