2026/2/6 15:34:55
网站建设
项目流程
建立什么网站,网站建设基本流程教学视频,开拓网站建设,进出口网站贸易平台有哪些1. 前言#xff1a;分页插件的发展与现状在Java持久层开发中#xff0c;分页是一个高频需求。目前主流的分页解决方案主要有两种#xff1a;PageHelper 和 MybatisPlus分页。两者各有优劣#xff1a;PageHelper#xff1a;老牌分页插件#xff0c;支持物理分页和内存分页…1. 前言分页插件的发展与现状在Java持久层开发中分页是一个高频需求。目前主流的分页解决方案主要有两种PageHelper和MybatisPlus分页。两者各有优劣PageHelper老牌分页插件支持物理分页和内存分页使用简单MybatisPlus分页MybatisPlus自带的分页功能与MP深度集成但在实际项目中我们经常会遇到这样的困境项目历史遗留代码使用PageHelper新模块希望使用MybatisPlus需要统一分页返回格式需要兼容两种分页方式本文将详细介绍如何打造一个兼容两者的增强版分页方案。2. PageHelper与MybatisPlus分页原理解析2.1 PageHelper工作原理java// PageHelper核心原理基于Mybatis拦截器 Intercepts(Signature( type Executor.class, method query, args {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} )) public class PageInterceptor implements Interceptor { // 1. 拦截Executor.query方法 // 2. 解析分页参数 // 3. 生成count查询和分页查询SQL // 4. 执行查询并封装结果 }2.2 MybatisPlus分页原理java// MybatisPlus分页拦截器 Component Order(0) Intercepts({Signature( type StatementHandler.class, method prepare, args {Connection.class, Integer.class} )}) public class PaginationInterceptor implements Interceptor { // 1. 拦截StatementHandler.prepare // 2. 检测是否需要分页 // 3. 改造SQL语句 // 4. 支持多种数据库方言 }2.3 两者差异对比特性PageHelperMybatisPlus分页启动方式PageHelper.startPage()配置PaginationInterceptor返回值PageInfoIPage线程安全使用ThreadLocal需手动清除每次new Page对象SQL解析使用JSqlParser使用自研解析器多租户支持需要额外配置内置支持性能较好优秀功能扩展插件体系内置功能丰富3. 兼容性设计方案3.1 设计目标无缝兼容无需修改现有代码统一API提供一致的使用体验灵活配置支持按需启用/禁用性能优化避免双重分页3.2 整体架构设计text┌─────────────────────────────────────────┐ │ 业务层调用 │ ├─────────────────────────────────────────┤ │ 统一分页门面(PageHelper/MP自适应) │ ├───────────────┬─────────────────────────┤ │ PageHelper适配器 │ MybatisPlus适配器 │ ├───────────────┴─────────────────────────┤ │ 原生Mybatis执行层 │ └─────────────────────────────────────────┘4. 核心实现代码4.1 统一分页配置类javaConfiguration ConditionalOnClass({SqlSessionFactory.class, MybatisPlusInterceptor.class}) AutoConfigureAfter({MybatisPlusAutoConfiguration.class}) public class UnifiedPaginationConfig { /** * 统一分页拦截器 * 优先使用MybatisPlus分页降级使用PageHelper */ Bean ConditionalOnMissingBean public MybatisPlusInterceptor mybatisPlusInterceptor( Autowired(required false) PaginationProperties properties) { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 自定义分页插件 PaginationInnerInterceptor paginationInterceptor new EnhancedPaginationInnerInterceptor(); // 设置数据库类型 paginationInterceptor.setDbType(DbType.MYSQL); // 设置分页参数合理化 paginationInterceptor.setOverflow(true); paginationInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } /** * PageHelper配置条件注册 */ Bean ConditionalOnProperty(name pagehelper.enabled, havingValue true) ConditionalOnMissingBean public PageInterceptor pageInterceptor() { Properties properties new Properties(); properties.setProperty(reasonable, true); properties.setProperty(supportMethodsArguments, true); properties.setProperty(params, countcountSql); properties.setProperty(autoRuntimeDialect, true); PageInterceptor pageInterceptor new PageInterceptor(); pageInterceptor.setProperties(properties); return pageInterceptor; } }4.2 增强版分页拦截器java/** * 增强版分页拦截器 - 兼容PageHelper和MP */ public class EnhancedPaginationInnerInterceptor extends PaginationInnerInterceptor { private static final ThreadLocalPage? PAGE_INFO new ThreadLocal(); Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 检测是否启用了PageHelper if (isPageHelperEnabled(parameter)) { // 兼容PageHelper调用 handlePageHelperCompatibility(parameter); return; } // 正常MP分页流程 super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql); } /** * 检测PageHelper分页 */ private boolean isPageHelperEnabled(Object parameter) { try { // 通过反射检测PageHelper的ThreadLocal变量 Class? pageHelperClass Class.forName(com.github.pagehelper.PageHelper); Field field pageHelperClass.getDeclaredField(LOCAL_PAGE); field.setAccessible(true); Object localPage field.get(null); if (localPage instanceof ThreadLocal) { ThreadLocal? threadLocal (ThreadLocal?) localPage; return threadLocal.get() ! null; } } catch (Exception e) { // 忽略异常表示PageHelper未启用 } return false; } /** * 处理PageHelper兼容性 */ private void handlePageHelperCompatibility(Object parameter) { try { // 获取PageHelper的分页参数 Class? pageClass Class.forName(com.github.pagehelper.Page); Class? pageHelperClass Class.forName(com.github.pagehelper.PageHelper); Method getPageMethod pageHelperClass.getMethod(getLocalPage); Object page getPageMethod.invoke(null); if (page ! null) { // 转换为MP的Page对象 Method getPageNumMethod pageClass.getMethod(getPageNum); Method getPageSizeMethod pageClass.getMethod(getPageSize); int pageNum (int) getPageNumMethod.invoke(page); int pageSize (int) getPageSizeMethod.invoke(page); // 创建MP Page对象 Page? mpPage new Page(pageNum, pageSize); PAGE_INFO.set(mpPage); // 设置到参数中 if (parameter instanceof Map) { ((Map) parameter).put(page, mpPage); } } } catch (Exception e) { throw new RuntimeException(PageHelper兼容处理失败, e); } } Override public void afterQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, Throwable throwable) { // 清理ThreadLocal PAGE_INFO.remove(); super.afterQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql, throwable); } }4.3 统一分页工具类java/** * 统一分页工具类 * 支持PageHelper和MybatisPlus两种方式 */ public class PageUtils { private static final boolean MP_EXISTS; private static final boolean PAGE_HELPER_EXISTS; static { MP_EXISTS ClassUtils.isPresent( com.baomidou.mybatisplus.extension.plugins.pagination.Page, PageUtils.class.getClassLoader() ); PAGE_HELPER_EXISTS ClassUtils.isPresent( com.github.pagehelper.PageHelper, PageUtils.class.getClassLoader() ); } /** * 开始分页 - 自动选择分页方式 */ public static T void startPage(PageParam param) { if (MP_EXISTS !PAGE_HELPER_EXISTS) { startPageByMP(param); } else if (PAGE_HELPER_EXISTS) { startPageByPageHelper(param); } else { throw new RuntimeException(未找到可用的分页插件); } } /** * 使用MybatisPlus分页 */ private static T void startPageByMP(PageParam param) { PageT page new Page( param.getPageNum(), param.getPageSize() ); // 设置排序 if (StringUtils.isNotBlank(param.getOrderBy())) { page.addOrder(param.getOrderBy(), param.isAsc()); } // 存储到请求上下文 RequestContextHolder.setPage(page); } /** * 使用PageHelper分页 */ private static void startPageByPageHelper(PageParam param) { try { Class? pageHelperClass Class.forName(com.github.pagehelper.PageHelper); Method startPageMethod pageHelperClass.getMethod( startPage, int.class, int.class, String.class ); startPageMethod.invoke(null, param.getPageNum(), param.getPageSize(), param.getOrderBy() ); } catch (Exception e) { throw new RuntimeException(PageHelper分页失败, e); } } /** * 转换分页结果为统一格式 */ public static T PageResultT convertToPageResult(Object pageObject) { if (pageObject null) { return PageResult.empty(); } // 判断来源 if (pageObject instanceof IPage) { return convertFromMPPage((IPageT) pageObject); } else if (isPageHelperPage(pageObject)) { return convertFromPageHelper(pageObject); } else if (pageObject instanceof List) { return convertFromList((ListT) pageObject); } else { throw new IllegalArgumentException(不支持的分页类型: pageObject.getClass()); } } /** * 从MP Page转换 */ private static T PageResultT convertFromMPPage(IPageT page) { PageResultT result new PageResult(); result.setList(page.getRecords()); result.setTotal(page.getTotal()); result.setPageNum((int) page.getCurrent()); result.setPageSize((int) page.getSize()); result.setPages((int) page.getPages()); result.setHasNext(page.getCurrent() page.getPages()); result.setHasPrevious(page.getCurrent() 1); return result; } /** * 从PageHelper转换 */ private static T PageResultT convertFromPageHelper(Object pageObject) { try { Class? pageInfoClass Class.forName(com.github.pagehelper.PageInfo); Method getListMethod pageInfoClass.getMethod(getList); Method getTotalMethod pageInfoClass.getMethod(getTotal); Method getPageNumMethod pageInfoClass.getMethod(getPageNum); Method getPageSizeMethod pageInfoClass.getMethod(getPageSize); Method getPagesMethod pageInfoClass.getMethod(getPages); ListT list (ListT) getListMethod.invoke(pageObject); long total (long) getTotalMethod.invoke(pageObject); int pageNum (int) getPageNumMethod.invoke(pageObject); int pageSize (int) getPageSizeMethod.invoke(pageObject); int pages (int) getPagesMethod.invoke(pageObject); PageResultT result new PageResult(); result.setList(list); result.setTotal(total); result.setPageNum(pageNum); result.setPageSize(pageSize); result.setPages(pages); result.setHasNext(pageNum pages); result.setHasPrevious(pageNum 1); return result; } catch (Exception e) { throw new RuntimeException(PageHelper结果转换失败, e); } } }4.4 统一分页注解java/** * 统一分页注解 * 支持在Controller方法上使用 */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface UnifiedPageable { /** * 当前页码默认第1页 */ int page() default 1; /** * 每页显示条数默认20条 */ int size() default 20; /** * 排序字段格式字段名,asc|desc */ String[] sort() default {}; /** * 是否统计总数默认true */ boolean count() default true; /** * 分页方式auto-自动选择mp-MybatisPlusph-PageHelper */ PageType type() default PageType.AUTO; enum PageType { AUTO, MP, PAGE_HELPER } } /** * 分页参数切面 */ Aspect Component Slf4j public class UnifiedPageableAspect { Around(annotation(unifiedPageable)) public Object around(ProceedingJoinPoint joinPoint, UnifiedPageable unifiedPageable) throws Throwable { // 解析请求参数 Object[] args joinPoint.getArgs(); PageParam pageParam extractPageParam(args, unifiedPageable); // 开始分页 PageUtils.startPage(pageParam); try { // 执行方法 Object result joinPoint.proceed(args); // 转换分页结果 return PageUtils.convertToPageResult(result); } finally { // 清理分页参数 if (PAGE_HELPER_EXISTS) { try { Class? pageHelperClass Class.forName(com.github.pagehelper.PageHelper); Method clearPageMethod pageHelperClass.getMethod(clearPage); clearPageMethod.invoke(null); } catch (Exception e) { log.warn(清理PageHelper分页参数失败, e); } } } } }4.5 请求上下文管理java/** * 请求上下文管理器 */ public class RequestContextHolder { private static final ThreadLocalMapString, Object CONTEXT ThreadLocal.withInitial(HashMap::new); /** * 设置分页对象 */ public static void setPage(Page? page) { getContext().put(page, page); } /** * 获取分页对象 */ SuppressWarnings(unchecked) public static T PageT getPage() { return (PageT) getContext().get(page); } /** * 清理上下文 */ public static void clear() { CONTEXT.remove(); } private static MapString, Object getContext() { return CONTEXT.get(); } }5. 使用示例5.1 Controller层统一使用javaRestController RequestMapping(/api/users) public class UserController { Autowired private UserService userService; /** * 方式1使用注解自动分页 */ GetMapping(/page) UnifiedPageable(page 1, size 20, sort {createTime,desc}) public PageResultUserVO queryUsers(UserQuery query) { // 直接返回Mapper或Service的查询结果 return userService.queryUsers(query); } /** * 方式2手动分页 */ GetMapping(/list) public PageResultUserVO listUsers( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 20) Integer pageSize, UserQuery query) { // 手动调用分页工具 PageUtils.startPage(new PageParam(pageNum, pageSize, id desc)); ListUserVO list userService.listUsers(query); // 自动转换分页结果 return PageUtils.convertToPageResult(list); } }5.2 Service层兼容两种方式javaService Slf4j public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; /** * 方案A使用MybatisPlus原生分页 */ Override public IPageUser queryUsersByMP(UserQuery query) { // 创建MP的Page对象 PageUser page new Page( query.getPageNum(), query.getPageSize() ); // 构建查询条件 LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()); wrapper.eq(query.getStatus() ! null, User::getStatus, query.getStatus()); wrapper.orderByDesc(User::getCreateTime); // 执行分页查询 return userMapper.selectPage(page, wrapper); } /** * 方案B使用PageHelper分页 */ Override public PageInfoUser queryUsersByPageHelper(UserQuery query) { // PageHelper会自动分页 ListUser users userMapper.selectByCondition(query); return new PageInfo(users); } /** * 方案C使用统一分页接口 */ Override public PageResultUserVO queryUsers(UserQuery query) { // 统一分页工具会自动处理 ListUser users userMapper.selectByCondition(query); // 转换VO ListUserVO vos convertToVO(users); // 返回统一格式 return new PageResult(vos); } }5.3 Mapper层适配javaMapper public interface UserMapper extends BaseMapperUser { /** * 方式1使用MP分页推荐 */ IPageUser selectPageByCondition( Param(page) IPageUser page, Param(query) UserQuery query ); /** * 方式2使用PageHelper分页 */ ListUser selectByCondition(Param(query) UserQuery query); /** * 方式3使用自定义SQL兼容两种 */ Select(script SELECT * FROM user WHERE 11 if testquery.name ! null AND name LIKE CONCAT(%,#{query.name},%)/if if testquery.status ! null AND status #{query.status}/if /script) ListUser selectByCondition2(Param(query) UserQuery query); }6. 配置管理6.1 配置文件yaml# application.yml mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 # 分页配置 page: enable-unified: true # 启用统一分页 default-page-size: 20 # 默认每页条数 max-page-size: 1000 # 最大每页条数 overflow: true # 溢出总页数后处理 optimize-count: true # 优化count查询 # PageHelper配置可选 pagehelper: enabled: false # 默认禁用按需开启 helper-dialect: mysql reasonable: true support-methods-arguments: true params: countcountSql6.2 配置类javaConfigurationProperties(prefix page) Data public class PageProperties { /** * 是否启用统一分页 */ private boolean enableUnified true; /** * 默认分页大小 */ private int defaultPageSize 20; /** * 最大分页大小 */ private int maxPageSize 1000; /** * 溢出处理 */ private boolean overflow true; /** * 优化COUNT查询 */ private boolean optimizeCount true; /** * 分页方式 */ private PageMode mode PageMode.AUTO; public enum PageMode { AUTO, MP, PAGE_HELPER } }7. 高级特性7.1 动态数据源分页java/** * 多数据源分页支持 */ public class DynamicDataSourcePaginationInterceptor extends PaginationInnerInterceptor { Autowired private DynamicDataSource dataSource; Override public Dialect getDialect(Executor executor) { // 根据当前数据源选择方言 String dsKey DynamicDataSourceContextHolder.getDataSourceKey(); DataSource dataSource this.dataSource.getDataSource(dsKey); // 获取数据库类型 String dbType getDbType(dataSource); return DialectFactory.getDialect(dbType); } }7.2 分页缓存优化java/** * 分页缓存管理器 */ Component Slf4j public class PageCacheManager { Autowired private RedisTemplateString, Object redisTemplate; /** * 缓存分页结果 */ public T PageResultT cachePageResult(String cacheKey, SupplierPageResultT supplier, long ttl, TimeUnit unit) { // 尝试从缓存获取 PageResultT cached getFromCache(cacheKey); if (cached ! null) { return cached; } // 查询并缓存 PageResultT result supplier.get(); putToCache(cacheKey, result, ttl, unit); return result; } /** * 生成分页缓存键 */ public String generatePageCacheKey(String prefix, Object query, int pageNum, int pageSize) { String queryHash DigestUtils.md5DigestAsHex( JSON.toJSONString(query).getBytes() ); return String.format(%s:%s:%d:%d, prefix, queryHash, pageNum, pageSize); } }7.3 分页性能监控java/** * 分页性能监控 */ Aspect Component Slf4j public class PagePerformanceAspect { private static final ThreadLocalLong START_TIME new ThreadLocal(); Around(execution(* *..*Mapper.*Page*(..))) public Object monitorPageQuery(ProceedingJoinPoint joinPoint) throws Throwable { START_TIME.set(System.currentTimeMillis()); try { return joinPoint.proceed(); } finally { long cost System.currentTimeMillis() - START_TIME.get(); if (cost 1000) { // 超过1秒记录警告日志 log.warn(分页查询耗时过长: {}ms, 方法: {}, cost, joinPoint.getSignature().getName()); } START_TIME.remove(); } } }8. 常见问题与解决方案8.1 问题1PageHelper与MP分页冲突现象同时启用两个分页插件导致分页失效或重复分页解决方案javaConfiguration public class PaginationConflictSolution { /** * 方案1互斥配置 */ Bean ConditionalOnMissingBean(PageInterceptor.class) public MybatisPlusInterceptor mybatisPlusInterceptor() { // 仅当PageHelper不存在时注册MP分页 } /** * 方案2统一接管 */ Bean public UnifiedPaginationInterceptor unifiedInterceptor() { // 使用统一拦截器接管所有分页逻辑 } }8.2 问题2分页参数传递问题现象Controller接收的分页参数无法传递到Service层解决方案java/** * 分页参数自动绑定 */ ControllerAdvice public class PageParamAdvice implements WebMvcConfigurer { Override public void addArgumentResolvers(ListHandlerMethodArgumentResolver resolvers) { resolvers.add(new PageParamArgumentResolver()); } static class PageParamArgumentResolver implements HandlerMethodArgumentResolver { Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(PageParam.class); } Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request (HttpServletRequest) webRequest.getNativeRequest(); String pageNum request.getParameter(pageNum); String pageSize request.getParameter(pageSize); String orderBy request.getParameter(orderBy); return new PageParam( StringUtils.isNotBlank(pageNum) ? Integer.parseInt(pageNum) : 1, StringUtils.isNotBlank(pageSize) ? Integer.parseInt(pageSize) : 20, orderBy ); } } }8.3 问题3复杂SQL分页性能问题现象多表关联查询分页性能差解决方案java/** * 分页优化策略 */ public class PageOptimizationStrategy { /** * 策略1使用延迟关联 */ public PageResultUserDTO queryUsersWithOptimization(UserQuery query) { // 1. 先分页查询主键 PageLong idPage userMapper.selectPageIds(query); if (CollectionUtils.isEmpty(idPage.getRecords())) { return PageResult.empty(); } // 2. 根据主键查询完整数据 ListUserDTO users userMapper.selectByIds(idPage.getRecords()); // 3. 组装结果 return new PageResult( users, idPage.getTotal(), idPage.getCurrent(), idPage.getSize() ); } /** * 策略2使用覆盖索引 */ Select(SELECT id, name FROM user WHERE status #{status} LIMIT #{offset}, #{limit}) ListUserSimpleVO selectSimpleByPage(Param(status) Integer status, Param(offset) Long offset, Param(limit) Integer limit); }9. 最佳实践建议9.1 项目迁移建议渐进式迁移新模块使用统一分页方案老模块逐步改造设立兼容层统一规范定义统一的分页返回格式制定分页参数命名规范建立分页性能标准9.2 性能优化建议SQL层面sql-- 避免使用 SELECT * SELECT id, name, age FROM user LIMIT 0, 20; -- 使用覆盖索引 SELECT id FROM user WHERE status 1 ORDER BY create_time DESC LIMIT 10000, 20;应用层面合理设置分页大小实现分页缓存监控慢分页查询9.3 代码规范建议Controller层java// ✅ 推荐 GetMapping UnifiedPageable public PageResultUserVO list(UserQuery query) { return userService.listUsers(query); } // ❌ 不推荐 GetMapping public MapString, Object list(RequestParam int page, RequestParam int size) { // 手动处理分页逻辑 }Service层java// ✅ 推荐使用统一分页工具 public PageResultUserVO listUsers(UserQuery query) { PageUtils.startPage(query.toPageParam()); ListUser users userMapper.selectList(query); return PageUtils.convertToPageResult(users); }10. 总结本文详细介绍了如何打造一个兼容PageHelper和MybatisPlus的增强版分页方案。关键要点统一抽象通过适配器模式统一两种分页方式无缝兼容支持现有代码平滑迁移灵活配置支持按需选择分页策略性能优化提供多种优化方案监控完善内置性能监控和问题排查实际项目中建议根据团队技术栈和项目需求选择合适的方案。对于新项目推荐直接使用MybatisPlus分页对于老项目迁移可以使用本文的统一分页方案作为过渡。核心价值降低学习成本团队成员只需掌握一套API提高开发效率减少重复的分页代码便于维护统一的分页逻辑和监控平滑迁移支持渐进式改造