1. MyBatis-Plus分页查询基础实现
MyBatis-Plus作为MyBatis的增强工具,提供了非常便捷的分页查询功能。我们先来看最基础的分页查询实现方式,这也是日常开发中最常用的场景。
1.1 基础分页查询实现
基础分页查询通常包含以下几个核心步骤:
- 创建分页对象(Page)
- 构建查询条件(QueryWrapper/LambdaQueryWrapper)
- 执行分页查询
- 返回分页结果
java复制@Override
public PageResult<Employee> selectPage(Integer pageNum, Integer pageSize, String name) {
// 1. 创建分页对象
Page<Employee> page = new Page<>(pageNum, pageSize);
// 2. 创建条件构造器
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
// 3. 添加查询条件
if (StringUtils.isNotBlank(name)) {
wrapper.like(Employee::getName, name);
}
// 4. 添加排序
wrapper.orderByDesc(Employee::getId);
// 5. 执行分页查询
IPage<Employee> result = employeeMapper.selectPage(page, wrapper);
// 6. 构建分页结果
return new PageResult<>(result.getRecords(), result.getTotal());
}
提示:LambdaQueryWrapper相比普通的QueryWrapper,使用Lambda表达式引用字段,可以避免硬编码字段名,减少因字段名变更导致的错误。
1.2 核心组件解析
Page对象:封装分页参数和结果
pageNum:当前页码pageSize:每页记录数records:查询结果列表total:总记录数
LambdaQueryWrapper:条件构造器
like:模糊查询eq:等于between:范围查询orderByDesc:降序排序
IPage接口:分页查询结果
- 继承Page对象,提供分页数据访问方法
1.3 注意事项与常见问题
- 页码处理:前端传递的页码通常从1开始,但需要确保pageNum和pageSize不为null且大于0
- 性能考虑:当数据量很大时,count查询可能很慢,可以考虑使用
page.setSearchCount(false)禁用count查询 - 排序字段:建议为排序字段建立索引,提高查询效率
- N+1问题:避免在循环中执行查询,应该使用批量查询或关联查询
2. 返回自定义VO的分页查询
在实际项目中,我们经常需要返回与数据库实体不同的视图对象(VO),这时就需要进行对象转换。
2.1 VO转换实现方式
java复制@Override
public PageResult<ArticleVO> selectPage(Integer pageNum, Integer pageSize, String name) {
// 1. 创建分页对象
Page<Article> page = new Page<>(pageNum, pageSize);
// 2. 创建条件构造器
LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>();
// 3. 添加查询条件
if (StringUtils.isNotBlank(name)) {
wrapper.like(Article::getUsername, name)
.or()
.like(Article::getTitle, name);
}
// 4. 添加排序
wrapper.orderByDesc(Article::getTime);
// 5. 执行分页查询
IPage<Article> result = articleMapper.selectPage(page, wrapper);
// 6. 转换为VO对象
List<ArticleVO> voList = result.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
// 7. 返回分页结果
return new PageResult<>(voList, result.getTotal());
}
private ArticleVO convertToVO(Article article) {
return ArticleVO.builder()
.id(article.getId())
.username(article.getUsername())
.title(article.getTitle())
.img(article.getImg())
.description(article.getDescription())
.content(article.getContent())
.time(article.getTime())
.build();
}
2.2 VO对象定义
VO对象通常使用Lombok简化代码:
java复制@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("article")
public class ArticleVO {
private Integer id;
private String username;
private String title;
private String img;
private String description;
private String content;
private String time;
}
2.3 转换策略选择
对象转换有多种实现方式:
- 手动转换:灵活可控,适合简单场景
- MapStruct:编译时生成转换代码,性能高
- ModelMapper:运行时反射,使用简单但性能较低
- BeanUtils.copyProperties:简单属性拷贝,不支持复杂转换
提示:对于性能敏感的场景,推荐使用MapStruct;对于简单项目,手动转换或BeanUtils可能更合适。
3. 关联查询的分页实现
当需要跨表查询时,MyBatis-Plus的基础分页方法就不够用了,我们需要自定义SQL实现关联查询。
3.1 关联查询实现方案
java复制@Override
public PageResult<EmployeeVO> selectPage(Integer pageNum, Integer pageSize, String name) {
// 1. 创建分页对象
Page<EmployeeVO> page = new Page<>(pageNum, pageSize);
// 2. 执行分页关联查询
IPage<EmployeeVO> result = employeeMapper.selectPageWithDept(page, name);
// 3. 构建分页结果
return new PageResult<>(result.getRecords(), result.getTotal());
}
Mapper接口定义:
java复制IPage<EmployeeVO> selectPageWithDept(Page<EmployeeVO> page, @Param("name") String name);
XML映射文件:
xml复制<select id="selectPageWithDept" resultType="vo.EmployeeVO">
SELECT e.*, d.name AS departmentName
FROM employee e
LEFT JOIN department d ON e.department_id = d.id
WHERE e.is_deleted = 0
AND e.role = 'EMPLOYEE'
<if test="name != null and name != ''">
AND e.name LIKE CONCAT('%', #{name}, '%')
</if>
ORDER BY e.id DESC
</select>
3.2 表连接类型详解
SQL中有几种主要的表连接方式:
-
LEFT JOIN(左连接)
- 返回左表所有记录,右表匹配不上的显示为NULL
- 适合需要主表完整数据的场景
-
RIGHT JOIN(右连接)
- 返回右表所有记录,左表匹配不上的显示为NULL
- 可以用LEFT JOIN改写,实际开发中较少使用
-
INNER JOIN(内连接)
- 只返回两表都匹配的记录
- 适合需要严格关联的场景
-
FULL OUTER JOIN(全连接)
- 返回两表所有记录,匹配不上的显示为NULL
- MySQL不支持,需用UNION模拟
3.3 关联查询性能优化
- 索引优化:确保关联字段和查询条件字段有索引
- 分页优化:避免使用
select *,只查询需要的字段 - 延迟加载:对于大字段或不常用字段,可以考虑延迟加载
- 缓存策略:对于不常变动的关联表数据,可以考虑缓存
4. 分页查询的高级应用
4.1 自定义分页插件
MyBatis-Plus的分页功能是通过分页插件实现的,我们可以自定义插件行为:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setMaxLimit(1000L); // 单页最大记录数
paginationInnerInterceptor.setOverflow(true); // 超出最大页数时回到第一页
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
4.2 多表关联分页的坑与解决方案
问题1:多表关联时count语句效率低
解决方案:
xml复制<select id="selectPageWithDept" resultType="vo.EmployeeVO">
SELECT e.*, d.name AS departmentName
FROM employee e
LEFT JOIN department d ON e.department_id = d.id
<where>
e.is_deleted = 0
AND e.role = 'EMPLOYEE'
<if test="name != null and name != ''">
AND e.name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
ORDER BY e.id DESC
</select>
<!-- 单独定义count查询 -->
<select id="selectPageWithDeptCount" resultType="long">
SELECT COUNT(1)
FROM employee e
<where>
e.is_deleted = 0
AND e.role = 'EMPLOYEE'
<if test="name != null and name != ''">
AND e.name LIKE CONCAT('%', #{name}, '%')
</if>
</where>
</select>
问题2:一对多关联导致分页数据不准确
解决方案:
- 先分页查询主表,再批量查询关联表
- 使用子查询先获取分页ID,再关联查询
4.3 分页查询的最佳实践
- 统一分页参数处理:封装分页请求和响应对象
- 参数校验:验证pageNum和pageSize的合法性
- 默认排序:为分页查询设置合理的默认排序
- 性能监控:记录分页查询耗时,优化慢查询
- 前端协作:与前端约定分页参数和响应格式
java复制// 统一分页请求对象
@Data
public class PageRequest {
private Integer pageNum = 1;
private Integer pageSize = 10;
private String orderBy;
private String orderDirection = "DESC";
public Page<?> toPage() {
return new Page<>(pageNum, pageSize);
}
}
// 统一分页响应对象
@Data
@Builder
public class PageResult<T> {
private List<T> list;
private Long total;
private Integer pageNum;
private Integer pageSize;
public static <T> PageResult<T> of(IPage<T> page) {
return PageResult.<T>builder()
.list(page.getRecords())
.total(page.getTotal())
.pageNum((int)page.getCurrent())
.pageSize((int)page.getSize())
.build();
}
}
在实际项目开发中,合理使用MyBatis-Plus的分页功能可以大大提高开发效率,但同时也要注意性能问题和复杂场景下的特殊处理。根据具体业务需求选择合适的分页实现方式,才能构建出既高效又稳定的分页查询功能。