
分页功能需协同控制器层与服务层:控制器负责接收并校验分页参数(如page、size),服务层则负责基于参数执行数据查询与总数统计,二者各司其职,共同保障分页的正确性、可测试性与可维护性。
分页功能需协同控制器层与服务层:控制器负责接收并校验分页参数(如page、size),服务层则负责基于参数执行数据查询与总数统计,二者各司其职,共同保障分页的正确性、可测试性与可维护性。
在典型的分层架构(如 Spring MVC)中,分页并非单一职责,而是一个横跨表现层与业务逻辑层的协作过程。将全部分页逻辑堆砌于控制器或服务层,都会带来设计缺陷:若仅在控制器中计算总页数、拼装 SQL 或手动截取 List,会严重违反关注点分离原则,导致业务逻辑泄露、单元测试困难;若强制服务层直接处理 HTTP 请求参数(如 @RequestParam 注解对象),又会使服务层耦合 Web 层细节,丧失复用性与可移植性。
✅ 正确分工如下:
-
控制器层(Controller):
- 接收并校验客户端传入的分页参数(如 page=1, size=10, sort=name,asc);
- 将参数转换为统一的分页请求对象(如 Pageable 或自定义 PageQuery);
- 调用服务方法,传入该对象;
- 将服务返回的分页结果(如 Page
或 PagedResult )封装为标准响应体(如 ResponseEntity >)。
-
服务层(Service):
- 接收标准化的分页参数(不依赖任何 Web 框架类型);
- 执行核心逻辑:调用 DAO/Repository 获取带分页的数据列表 + 总记录数(通常通过单条 COUNT(*) 查询 + 分页 SELECT 实现,或利用数据库原生分页支持如 MySQL LIMIT OFFSET、PostgreSQL OFFSET FETCH);
- 返回结构化分页结果(推荐使用 org.springframework.data.domain.Page
或自定义 DTO,如含 content, totalElements, totalPages, number, size 等字段)。
示例代码(Spring Boot 风格):
// Controller 层
@GetMapping("/users")
public ResponseEntity<PageResponse<User>> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("name").ascending());
Page<User> userPage = userService.findUsersByPage(pageable);
return ResponseEntity.ok(PageResponse.of(userPage));
}
// Service 层(接口定义清晰,无 Web 依赖)
public interface UserService {
Page<User> findUsersByPage(Pageable pageable);
}
// Service 实现
@Service
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
@Override
public Page<User> findUsersByPage(Pageable pageable) {
// Spring Data JPA 自动处理 COUNT + LIMIT 查询
return userRepository.findAll(pageable);
// 或手动实现(适用于复杂查询):
// long total = userRepository.countByConditions(...);
// List<User> content = userRepository.findWithPagination(..., pageable);
// return new PageImpl<>(content, pageable, total);
}
}⚠️ 注意事项:
- 避免在 Controller 中手动 list.subList() 截取数据——这无法规避全表扫描,性能灾难;
- 不建议在 Service 中解析 HttpServletRequest 或直接读取 @RequestParam——破坏分层边界;
- 对于高并发场景,可考虑缓存 totalElements(如结合 Redis),但需权衡数据实时性;
- 若使用 MyBatis,推荐配合 PageHelper.startPage() 或 RowBounds,但务必确保 Service 方法内完成分页上下文管理,Controller 不介入。
总结而言,分页不是“该放在哪一层”的二选一问题,而是“如何协同分层”的设计命题。控制器专注参数流转与协议适配,服务层专注数据获取与业务规则,二者通过契约化接口(如 Pageable / Page










