别再写重复的Controller了!Spring Boot 3.x + Pageable 实现分页查询的5个最佳实践

张开发
2026/4/17 9:08:10 15 分钟阅读

分享文章

别再写重复的Controller了!Spring Boot 3.x + Pageable 实现分页查询的5个最佳实践
Spring Boot 3.x分页查询工程化实践从Controller优化到架构设计每次打开IDE看到那些重复的分页查询代码我都忍不住想重构。分页查询作为业务系统的高频操作却在大多数项目中以最原始的方式被复制粘贴。今天我们就来聊聊如何用Spring Boot 3.x和Pageable打造一套工程化的分页解决方案让你的代码摆脱CtrlC/V的诅咒。1. 分页参数处理的标准化改造传统分页代码最让人头疼的就是满屏的pageNum和pageSize参数校验。在Spring Boot 3.x中我们可以通过自定义参数解析器来统一处理这些重复逻辑。首先创建分页参数DTOpublic class PageParam { Min(1) private int page 1; Max(100) private int size 10; private String sort; // getters/setters }然后实现HandlerMethodArgumentResolverpublic class PageableArgumentResolver implements HandlerMethodArgumentResolver { Override public boolean supportsParameter(MethodParameter parameter) { return parameter.getParameterType().equals(Pageable.class); } Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request (HttpServletRequest) webRequest.getNativeRequest(); int page Integer.parseInt(request.getParameter(page)); int size Integer.parseInt(request.getParameter(size)); String sort request.getParameter(sort); return PageRequest.of(page - 1, size, Sort.by(sort)); } }注册解析器Configuration public class WebConfig implements WebMvcConfigurer { Override public void addArgumentResolvers(ListHandlerMethodArgumentResolver resolvers) { resolvers.add(new PageableArgumentResolver()); } }现在Controller可以简化为GetMapping(/users) public PageUser getUsers(Pageable pageable) { return userService.findAll(pageable); }2. 响应体的统一封装艺术直接返回Spring Data的Page对象会暴露过多实现细节我们需要自定义响应结构public class PageResultT { private ListT content; private Pagination pagination; public static T PageResultT of(PageT page) { PageResultT result new PageResult(); result.setContent(page.getContent()); Pagination pagination new Pagination(); pagination.setPage(page.getNumber() 1); pagination.setSize(page.getSize()); pagination.setTotalPages(page.getTotalPages()); pagination.setTotalElements(page.getTotalElements()); result.setPagination(pagination); return result; } // 省略getter/setter }配合全局响应处理器RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return true; } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { if (body instanceof Page) { return PageResult.of((Page?) body); } return body; } }3. 复杂查询条件的优雅处理当分页遇上多条件查询时代码很容易变得臃肿。我们可以使用Specification来保持代码整洁public class UserSpecifications { public static SpecificationUser nameLike(String name) { return (root, query, cb) - name null ? null : cb.like(root.get(name), % name %); } public static SpecificationUser ageBetween(Integer minAge, Integer maxAge) { return (root, query, cb) - { if (minAge null maxAge null) return null; if (minAge ! null maxAge ! null) { return cb.between(root.get(age), minAge, maxAge); } else if (minAge ! null) { return cb.greaterThanOrEqualTo(root.get(age), minAge); } else { return cb.lessThanOrEqualTo(root.get(age), maxAge); } }; } }Service层使用public PageResultUser searchUsers(String name, Integer minAge, Integer maxAge, Pageable pageable) { SpecificationUser spec Specification.where(UserSpecifications.nameLike(name)) .and(UserSpecifications.ageBetween(minAge, maxAge)); return PageResult.of(userRepository.findAll(spec, pageable)); }4. 分页逻辑的AOP抽象对于需要分页的Service方法我们可以用自定义注解实现声明式分页Retention(RetentionPolicy.RUNTIME) Target(ElementType.METHOD) public interface Paginate { String pageParam() default page; String sizeParam() default size; String sortParam() default sort; }实现切面逻辑Aspect Component public class PaginationAspect { Around(annotation(paginate)) public Object around(ProceedingJoinPoint joinPoint, Paginate paginate) throws Throwable { Object[] args joinPoint.getArgs(); // 从参数中提取分页信息 Pageable pageable extractPageable(args, paginate); // 执行原方法 Object result joinPoint.proceed(args); // 处理返回结果 if (result instanceof List) { return PageResult.of(new PageImpl((List?) result, pageable, ((List?) result).size())); } return result; } private Pageable extractPageable(Object[] args, Paginate paginate) { // 实现参数提取逻辑 } }使用示例Paginate public ListUser findActiveUsers() { return userRepository.findByActiveTrue(); }5. 性能优化与特殊场景处理分页查询在高并发场景下容易成为性能瓶颈这里有几个优化技巧COUNT查询优化Query(value SELECT u FROM User u LEFT JOIN FETCH u.department, countQuery SELECT COUNT(u.id) FROM User u) PageUser findAllWithDepartment(Pageable pageable);游标分页实现public interface UserRepository extends JpaRepositoryUser, Long { SliceUser findFirst10ByOrderByIdAsc(Long lastId); }批量ID查询优化Query(SELECT u FROM User u WHERE u.id IN :ids) ListUser findByIds(Param(ids) ListLong ids, Sort sort); public PageResultUser findUsers(Pageable pageable) { PageLong ids userRepository.findUserIds(pageable); ListUser content userRepository.findByIds(ids.getContent(), pageable.getSort()); return new PageResult(content, ids.getTotalElements()); }这些方案在数据量达到百万级时性能差异可以达到10倍以上。

更多文章