多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

SpringBoot整合MyBatis分页插件PageHelper全解析

SpringBoot整合MyBatis分页插件PageHelper全解析 1. 分页处理的必要性与应用场景在开发企业级应用时数据分页几乎是每个项目都会遇到的刚需。想象一下当数据库中有10万条用户记录时如果一次性全部加载到内存中不仅会造成服务器内存压力前端渲染也会变得极其缓慢。这就是为什么我们需要分页处理——它像一本厚厚的书被拆分成多个章节让用户能够按需翻阅。SpringBoot作为Java生态中最流行的框架之一其分页方案的选择尤为重要。原生JPA虽然提供了Pageable接口但在复杂SQL场景下显得力不从心。MyBatis作为另一个主流ORM框架其分页插件PageHelper则凭借简单易用的特性成为众多开发者的首选。2. PageHelper核心原理剖析2.1 拦截器机制实现原理PageHelper本质上是一个MyBatis拦截器Interceptor它会在SQL执行前动态修改语句。当调用PageHelper.startPage()方法时它会将当前分页参数页码、每页条数存入ThreadLocal中。在后续执行Mapper查询时拦截器会检测到分页请求自动将原始SQL改写为带有LIMIT的分页查询。以MySQL为例原始SQLSELECT * FROM user会被改写为SELECT * FROM user LIMIT 0,102.2 两种依赖引入方式对比方式一传统starter依赖推荐dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.6/version /dependency优势自动配置零配置开箱即用与SpringBoot版本自动适配内置合理的默认参数方式二基础依赖手动配置dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper/artifactId version5.3.2/version /dependency需要在application.yml中配置pagehelper: helperDialect: mysql reasonable: true supportMethodsArguments: true适用场景需要高度定制化配置项目中使用非SpringBoot环境需要精确控制依赖版本3. 完整集成与使用指南3.1 基础配置实践在启动类添加MapperScan注解扫描Mapper接口SpringBootApplication MapperScan(com.example.mapper) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }3.2 核心API使用示例基本分页查询GetMapping(/users) public PageInfoUser getUsers(RequestParam(defaultValue 1) int pageNum, RequestParam(defaultValue 10) int pageSize) { // 关键分页设置 PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectAll(); return new PageInfo(users); }复杂查询带排序PageHelper.startPage(1, 10, create_time desc); ListUser users userMapper.selectByCondition(condition);3.3 分页结果包装技巧PageInfo对象包含丰富分页信息{ pageNum: 1, pageSize: 10, total: 100, pages: 10, list: [...], hasPreviousPage: false, hasNextPage: true }自定义返回结构public class PageResultT { private int current; private int size; private long total; private ListT records; public static T PageResultT of(PageInfoT pageInfo) { PageResultT result new PageResult(); result.setCurrent(pageInfo.getPageNum()); result.setSize(pageInfo.getPageSize()); result.setTotal(pageInfo.getTotal()); result.setRecords(pageInfo.getList()); return result; } }4. 高级特性与性能优化4.1 多数据源支持配置当项目中使用多个数据源时需要为每个SqlSessionFactory单独配置拦截器Bean ConfigurationProperties(prefix spring.datasource.db1) public DataSource db1DataSource() { return DataSourceBuilder.create().build(); } Bean public SqlSessionFactory db1SqlSessionFactory() throws Exception { SqlSessionFactoryBean factory new SqlSessionFactoryBean(); factory.setDataSource(db1DataSource()); // 关键配置点 Interceptor[] plugins {new PageInterceptor()}; factory.setPlugins(plugins); return factory.getObject(); }4.2 大数据量分页优化当处理百万级数据时传统LIMIT分页会出现性能问题。可以采用游标分页方案// 使用id作为游标 PageHelper.startPage(1, 10, id asc); ListUser users userMapper.selectAfterId(lastId);对应SQLSELECT * FROM user WHERE id #{lastId} ORDER BY id LIMIT 104.3 自定义Count查询对于复杂联表查询可以指定专门的count语句提升性能SelectProvider(type UserSqlProvider.class, method selectComplex) Options(countStatement COUNT_COMPLEX_SQL) ListUser selectComplex(QueryCondition condition);5. 常见问题排查指南5.1 分页失效场景分析调用顺序错误ListUser users userMapper.selectAll(); // 先执行查询 PageHelper.startPage(1, 10); // 后设置分页 → 失效线程污染问题PageHelper.startPage(1, 10); new Thread(() - { userMapper.selectAll(); // 新线程无法获取分页参数 }).start();未指定dialectDEBUG - [sql] SELECT * FROM user日志中没有出现LIMIT语句检查是否配置了正确的数据库方言5.2 特殊字符排序问题当使用动态排序时String orderBy name desc; PageHelper.startPage(1, 10, orderBy);如果排序字段来自前端输入必须进行过滤防止SQL注入String safeOrderBy Stream.of(orderBy.split(,)) .filter(s - s.matches(^[a-zA-Z0-9_]\\s(asc|desc)$)) .collect(Collectors.joining(,));5.3 与MyBatis-Plus的兼容性当同时使用PageHelper和MyBatis-Plus时建议排除MyBatis-Plus的分页插件dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version exclusions exclusion groupIdcom.baomidou/groupId artifactIdmybatis-plus-extension/artifactId /exclusion /exclusions /dependency或者在配置中明确指定使用PageHelpermybatis-plus: configuration: default-enum-type-handler: org.apache.ibatis.type.EnumTypeHandler # 禁用MP分页 use-deprecated-executor: false6. 生产环境最佳实践6.1 统一分页参数处理创建分页请求基类public class BasePageQuery { Min(1) private Integer pageNum 1; Min(1) Max(100) private Integer pageSize 10; private String orderBy; // 安全获取分页参数 public Page toPage() { return PageHelper.startPage(pageNum, pageSize, orderBy); } }6.2 全局分页响应封装使用ResponseBodyAdvice统一包装RestControllerAdvice public class PageResponseAdvice implements ResponseBodyAdviceObject { Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return PageInfo.class.isAssignableFrom(returnType.getParameterType()); } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { return ApiResult.success((PageInfo?)body); } }6.3 性能监控建议添加监控点统计分页查询耗时Aspect Component public class PageQueryMonitor { Around(execution(* com..mapper.*.*(..)) annotation(org.apache.ibatis.annotations.Select)) public Object monitorQuery(ProceedingJoinPoint pjp) throws Throwable { if (PageHelper.getLocalPage() ! null) { long start System.currentTimeMillis(); Object result pjp.proceed(); long cost System.currentTimeMillis() - start; Metrics.counter(page.query.cost).tag(method, pjp.getSignature().getName()) .record(cost); return result; } return pjp.proceed(); } }7. 扩展功能开发7.1 自定义分页插件继承PageInterceptor实现特殊逻辑public class CustomPageInterceptor extends PageInterceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 前置处理 if (needSpecialHandle()) { return handleSpecialCase(invocation); } // 默认处理 return super.intercept(invocation); } }7.2 多级分页支持处理一对多关系的分页// 先分页查询主表 PageHelper.startPage(1, 10); ListOrder orders orderMapper.selectOrders(); // 再批量查询关联明细 orders.forEach(order - { PageHelper.startPage(1, 5); ListOrderItem items orderItemMapper.selectByOrderId(order.getId()); order.setItems(items); });7.3 分页缓存策略使用Redis缓存分页结果public PageInfoUser getUsersWithCache(int pageNum, int pageSize) { String cacheKey users: pageNum : pageSize; PageInfoUser pageInfo redisTemplate.opsForValue().get(cacheKey); if (pageInfo null) { PageHelper.startPage(pageNum, pageSize); ListUser users userMapper.selectAll(); pageInfo new PageInfo(users); redisTemplate.opsForValue().set(cacheKey, pageInfo, 5, TimeUnit.MINUTES); } return pageInfo; }在SpringBoot项目中合理使用PageHelper可以显著提升分页开发的效率和性能。根据项目复杂度选择适合的集成方式结合业务场景灵活运用各种高级特性同时注意规避常见的陷阱问题
返回列表