MyBatis-Plus进行分页查询优化的实践指南
作者:码农阿豪@新空间
在实际开发中,分页查询是常见的需求,尤其是需要关联其他表获取额外信息的场景,本文将介绍如何使用 MyBatis-Plus 进行高效分页查询,并结合关联查询优化数据填充,需要的可以了解下
1. 引言
在实际开发中,分页查询是常见的需求,尤其是需要关联其他表获取额外信息的场景。例如,在任务管理系统中,查询任务列表时,除了任务基本信息外,还需要显示任务创建者的用户名(通常存储在用户表中)。直接使用循环单条查询会导致性能问题,而合理的优化可以显著提升查询效率。
本文将介绍如何使用 MyBatis-Plus 进行高效分页查询,并结合关联查询优化数据填充,最终封装成 VO(View Object) 返回给前端。我们将分析两种优化方案:
- 单条查询填充(适用于数据量较小的情况)
- 批量查询填充(适用于大数据量,提高性能)
2. 基础分页查询实现
首先,我们回顾基础的 MyBatis-Plus 分页查询实现:
Page<TaskManagement> pageRequest = new Page<>(request.getPage(), request.getPageSize());
// 构建查询条件
LambdaQueryWrapper<TaskManagement> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(request.getStatus())) {
wrapper.like(TaskManagement::getStatus, request.getStatus());
}
// 执行分页查询
IPage<TaskManagement> taskManagementIPage = taskManagementService.page(pageRequest, wrapper);
这段代码完成了:
- 分页参数构建(
Page对象) - 动态条件查询(
LambdaQueryWrapper) - 执行分页查询(
taskManagementService.page())
但返回的数据仅包含 TaskManagement 表的信息,缺少关联的用户名(creatorUsername),我们需要进一步优化。
3. 优化方案1:单条查询填充(适用于小数据量)
3.1 定义VO对象
首先,定义一个 TaskManagementVO,增加 creatorUsername 字段:
@Data
public class TaskManagementVO {
private Long id;
private String taskName;
private String status;
// 其他任务字段...
private Long userId; // 创建者ID
private String creatorUsername; // 创建者姓名(需关联用户表查询)
}
3.2 转换分页数据并填充用户名
使用 IPage.convert() 方法进行数据转换,并在转换过程中查询用户名:
IPage<TaskManagementVO> voPage = taskManagementIPage.convert(task -> {
TaskManagementVO vo = new TaskManagementVO();
BeanUtils.copyProperties(task, vo); // 使用Spring BeanUtils复制属性
if (task.getUserId() != null) {
// 查询用户信息
User user = userService.getById(task.getUserId());
if (user != null) {
vo.setCreatorUsername(user.getName());
}
}
return vo;
});
return voPage;
3.3 优缺点分析
优点:
- 代码简单,易于理解
- 适用于数据量较小的场景
缺点:
- N+1查询问题:如果每页有N条数据,会额外执行N次用户查询,性能较差
- 不适合大数据量分页
4. 优化方案2:批量查询填充(适用于大数据量)
4.1 优化思路
- 先收集所有
userId - 批量查询用户数据,减少数据库交互
- 使用
Map存储用户信息,提高查询效率
4.2 代码实现
// 1. 提取所有不重复的userId
List<Long> userIds = taskManagementIPage.getRecords().stream()
.map(TaskManagement::getUserId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
// 2. 批量查询用户信息,并转为Map<userId, User>
Map<Long, User> userMap = userIds.isEmpty()
? Collections.emptyMap()
: userService.listByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
// 3. 转换分页数据并填充用户名
IPage<TaskManagementVO> voPage = taskManagementIPage.convert(task -> {
TaskManagementVO vo = new TaskManagementVO();
BeanUtils.copyProperties(task, vo);
if (task.getUserId() != null && userMap.containsKey(task.getUserId())) {
vo.setCreatorUsername(userMap.get(task.getUserId()).getName());
}
return vo;
});
return voPage;
4.3 优化点分析
优点:
- 减少数据库查询次数:无论分页数据有多少条,用户表只查询1次
- 性能更高:适合大数据量场景
缺点:
- 代码稍复杂,需要额外处理
Map结构 - 如果
userIds过多(如 IN 查询超过1000条),可能需要分批查询
5. 进一步优化:使用JOIN查询(SQL层面优化)
如果 TaskManagement 和 User 表关联频繁,可以在 SQL 层面直接使用 JOIN 查询,避免多次查询:
5.1 自定义SQL查询
<!-- TaskManagementMapper.xml -->
<select id="selectTaskPageWithUser" resultType="TaskManagementVO">
SELECT
t.*,
u.name AS creatorUsername
FROM
task_management t
LEFT JOIN
user u ON t.user_id = u.id
<where>
<if test="status != null and status != ''">
t.status LIKE CONCAT('%', #{status}, '%')
</if>
</where>
</select>
5.2 在Service层调用
// 使用自定义分页查询
IPage<TaskManagementVO> voPage = taskManagementMapper.selectTaskPageWithUser(
pageRequest,
request.getStatus());
5.3 优缺点
优点:
- 最高效:只需1次SQL查询
- 适合复杂关联查询
缺点:
- 需要手写SQL,不够灵活
- 如果关联表过多,SQL可能较复杂
6. 总结与最佳实践
| 方案 | 适用场景 | 性能 | 代码复杂度 |
|---|---|---|---|
| 单条查询填充 | 数据量小(<100条/页) | 较差(N+1查询) | 低 |
| 批量查询填充 | 数据量较大(100-1000条/页) | 较好(2次查询) | 中 |
| JOIN查询 | 大数据量、频繁关联查询 | 最优(1次查询) | 高(需手写SQL) |
6.1 推荐选择
- 小型项目/简单查询 → 单条查询填充
- 中大型项目/数据量较大 → 批量查询填充
- 高性能要求/复杂关联 → JOIN查询
6.2 完整优化代码示例
public IPage<TaskManagementVO> getTaskPage(TaskPageRequest request) {
// 1. 构建分页查询
Page<TaskManagement> pageRequest = new Page<>(request.getPage(), request.getPageSize());
LambdaQueryWrapper<TaskManagement> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(request.getStatus())) {
wrapper.like(TaskManagement::getStatus, request.getStatus());
}
// 2. 执行分页查询
IPage<TaskManagement> taskManagementIPage = taskManagementService.page(pageRequest, wrapper);
// 3. 批量查询用户信息
List<Long> userIds = taskManagementIPage.getRecords().stream()
.map(TaskManagement::getUserId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
Map<Long, User> userMap = userIds.isEmpty()
? Collections.emptyMap()
: userService.listByIds(userIds).stream()
.collect(Collectors.toMap(User::getId, Function.identity()));
// 4. 转换VO并填充用户名
return taskManagementIPage.convert(task -> {
TaskManagementVO vo = new TaskManagementVO();
BeanUtils.copyProperties(task, vo);
if (task.getUserId() != null && userMap.containsKey(task.getUserId())) {
vo.setCreatorUsername(userMap.get(task.getUserId()).getName());
}
return vo;
});
}
7. 结语
优化分页查询的核心在于 减少数据库交互次数,本文介绍了三种优化方案,适用于不同场景。在实际开发中,应根据业务需求和数据量选择最合适的方案。
进一步优化建议:
- 使用 缓存(Redis) 存储用户信息,减少数据库查询
- 对于超大数据量(如10万+),考虑 游标分页 或 Elasticsearch 优化
到此这篇关于MyBatis-Plus进行分页查询优化的实践指南的文章就介绍到这了,更多相关mybatis-plus分页查询内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
