好记忆不如烂笔头,能记下点东西,就记下点,有时间拿出来看看,也会发觉不一样的感受.
目录
一、基础概念
MyBatis Plus 是一个增强版的 MyBatis 框架,提供了强大的分页功能,通过内置的分页插件可以轻松实现分页查询。其分页功能的核心是通过拦截器 PaginationInterceptor
实现的,该拦截器会在执行 SQL 之前对 SQL 进行解析和改写,为查询语句添加 LIMIT
和 OFFSET
子句,并生成统计总记录数的 SQL。
二、常见分页问题及优化策略
(一)单表大分页问题
当数据量较大时,分页查询效率会显著下降,尤其是当 offset
值较大时,数据库需要扫描大量数据才能返回结果。
1. 利用主键索引优化
如果表有主键索引,可以先查询主键,再根据主键查询具体数据,减少数据扫描量。
Page<Long> idPage = new Page<>(2, 10);
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id");
userMapper.selectPage(idPage, wrapper);
List<Long> ids = idPage.getRecords();
QueryWrapper<User> dataWrapper = new QueryWrapper<>();
dataWrapper.in("id", ids);
List<User> users = userMapper.selectList(dataWrapper);
2. 子查询优化
通过子查询获取主键范围,避免大量 offset
扫描。
SELECT u.* FROM user u
WHERE u.id > (SELECT id FROM user ORDER BY id LIMIT 9990, 1)
LIMIT 10;
3. 限制最大分页页数
在业务层面限制用户能够查询的最大页数,避免出现过大的 offset
。
(二)多表关联分页问题
多表关联查询时,分页的复杂性会增加,不同的关联方式会对分页结果产生不同影响。
1. 先分页再关联
如果需要对主表进行分页,应先对主表进行分页,再关联子表数据,避免关联后数据量过大导致的分页效率问题。
Page<User> userPage = new Page<>(2, 10);
userMapper.selectPage(userPage, null);
List<User> users = userPage.getRecords();
List<Order> orders = orderMapper.selectList(new QueryWrapper<Order>().in("user_id", users.stream().map(User::getId).collect(Collectors.toList())));
users.forEach(user -> user.setOrders(orders.stream().filter(order -> order.getUserId().equals(user.getId())).collect(Collectors.toList())));
(三)分页与排序结合的问题
当分页查询中包含排序时,如果排序字段存在重复值,可能会导致分页结果不一致。
1. 添加唯一字段辅助排序
在排序时加上一个唯一的字段(如主键),确保分页结果的稳定性。
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("create_time", "id");
Page<User> page = new Page<>(2, 10);
userMapper.selectPage(page, wrapper);
三、分页插件配置与优化
(一)插件配置
MyBatis Plus 的分页插件提供了多种可配置项,可以根据业务需求进行调整。
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setOverflow(false);
paginationInterceptor.setReasonable(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
(二)自定义分页参数
可以通过实现 Pagination
接口来自定义分页参数,例如限制每页的最大数据量。
public class CustomPage<T> extends Page<T> {
private static final long MAX_PAGE_SIZE = 100;
public CustomPage(long current, long size) {
super(current, size);
if (size > MAX_PAGE_SIZE) {
this.setSize(MAX_PAGE_SIZE);
}
}
}
(三)分页插件的局限性
分页插件在处理复杂 SQL(如包含子查询、UNION
等操作)时可能无法正确解析和改写,需要手动处理分页或优化 SQL。
四、实战案例 —— 电商订单列表分页优化
(一)问题分析
-
数据量庞大,分页查询效率可能下降。
-
多表关联复杂,关联方式和顺序影响查询效率。
-
排序和筛选条件多样,需要考虑各种组合情况。
(二)优化方案
-
索引优化:为订单表的
status
、create_time
等常用筛选和排序字段添加索引。 -
分页策略:先对主表(订单表)进行分页,再关联其他表数据。
-
结果集优化:只查询需要的字段,减少数据传输量。
-
缓存机制:使用缓存(如 Redis)减少数据库访问压力。
(三)代码实现
public Page<OrderVO> getOrderPage(OrderQueryParam param) {
QueryWrapper<Order> orderWrapper = new QueryWrapper<>();
orderWrapper.eq(param.getStatus() != null, "status", param.getStatus());
orderWrapper.between(param.getStartTime() != null && param.getEndTime() != null, "create_time", param.getStartTime(), param.getEndTime());
orderWrapper.orderByDesc("create_time", "id");
Page<Order> orderPage = new Page<>(param.getCurrentPage(), param.getPageSize());
userMapper.selectPage(orderPage, orderWrapper);
List<Long> orderIds = orderPage.getRecords().stream().map(Order::getId).collect(Collectors.toList());
List<User> users = userMapper.selectList(new QueryWrapper<User>().in("id", orderPage.getRecords().stream().map(Order::getUserId).collect(Collectors.toList())));
List<Product> products = productMapper.selectList(new QueryWrapper<Product>().in("id", orderPage.getRecords().stream().map(Order::getProductId).collect(Collectors.toList())));
List<OrderVO> orderVOList = orderPage.getRecords().stream().map(order -> {
OrderVO orderVO = new OrderVO();
BeanUtils.copyProperties(order, orderVO);
User user = users.stream().filter(u -> u.getId().equals(order.getUserId())).findFirst().orElse(null);
if (user != null) {
orderVO.setUserName(user.getUserName());
}
Product product = products.stream().filter(p -> p.getId().equals(order.getProductId())).findFirst().orElse(null);
if (product != null) {
orderVO.setProductName(product.getProductName());
}
return orderVO;
}).collect(Collectors.toList());
Page<OrderVO> resultPage = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
resultPage.setRecords(orderVOList);
return resultPage;
}
五、面试应答技巧
当面试官问到 MyBatis Plus 分页优化问题时,我们可以按照以下思路来回答:
-
基础用法:先简要说明 MyBatis Plus 分页的基本使用方法,展示自己对基础的掌握。
-
原理分析:解释分页插件的实现原理,比如拦截器的作用,SQL 的改写过程,体现自己对底层原理的理解。
-
常见问题及解决方案:针对单表大分页、多表关联分页、分页与排序结合等问题,分别阐述遇到的问题、原因分析和解决方法,展示自己的实战经验。
-
深入优化:介绍分页插件的配置、自定义分页参数等内容,体现自己对框架的深入理解和扩展能力。
-
实战案例:结合实际项目中的案例,说明如何进行分页优化,展示自己的问题解决能力和项目经验。
总之,回答时要条理清晰,层次分明,既有理论分析,又有实战经验,让面试官感受到你对这个问题的深入理解和扎实的技术功底。
六、总结
MyBatis Plus 的分页功能虽然强大,但在面对大数据量和复杂查询时仍需优化。通过合理利用主键索引、优化 SQL 查询、合理配置分页插件以及引入缓存机制,可以有效提升分页查询的效率。在实际开发中,应根据具体业务需求灵活选择优化策略,确保系统的高性能和稳定性。