Controller 接收前端分頁參數 (page, pageSize),Service 查詢返回完整 List<T>,需在記憶體中截取指定頁面資料返回。因應不同業務實體類型 (User, Order, Product...),需編寫通用泛型方法。
/** * 通用列表分頁 * @param list 完整資料列表 * @param pageSize 每頁筆數 * @param pageNo 頁碼 (從 1 開始) * @param元素型別 * @return 指定頁面的子列表 */ public static List getPage(List list, int pageSize, int pageNo) { // 參數防禦 if (list == null || list.isEmpty() || pageSize <= 0 || pageNo <= 0) { return Collections.emptyList(); } int total = list.size(); int totalPages = (total + pageSize - 1) / pageSize; // 天花板除法 // 頁碼超出範圍返回空列表 (或拋出異常視業務而定) if (pageNo > totalPages) { return Collections.emptyList(); } // 計算起始/結束索引 (0-based) int fromIndex = (pageNo - 1) * pageSize; int toIndex = Math.min(fromIndex + pageSize, total); // 使用 subList 獲取視圖 (O(1),無複製開銷),再轉為新 ArrayList 斷開關聯 return new ArrayList<>(list.subList(fromIndex, toIndex)); }
| 決策點 | 選擇 | 理由 |
|---|---|---|
| 泛型宣告 | <T> List<T> getPage(List<T> ...) | 型別安全,編譯期檢查,呼叫端無需轉型 |
| 參數順序 | (list, pageSize, pageNo) | 資料源優先,配置參數在後 |
| 頁碼基準 | 1-based (pageNo=1 為第一頁) | 符合人類直覺與前端慣例 |
| 邊界處理 | 參數非法/超頁返回空列表 | 避免 NPE/IndexOutOfBounds,語義清晰 |
| 實作方式 | list.subList() + new ArrayList() | subList 是視圖零複製,新 ArrayList 斷開底層關聯避免副作用 |
| 回傳型別 | List<T> (非具體 ArrayList) | 面向介面編程,保留實作彈性 |
// Service 層 public ListgetUsersPage(int pageNo, int pageSize) { List allUsers = userDao.findAll(); // 查詢全量 (注意:大資料量慎用!) List pageUsers = PageUtils.getPage(allUsers, pageSize, pageNo); return pageUsers.stream().map(this::convertToVO).collect(Collectors.toList()); } // Controller 層 @GetMapping("/users") public ResponseEntity > getUsers( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { List data = userService.getUsersPage(page, size); long total = userDao.count(); // 總筆數單獨查詢 return ResponseEntity.ok(new PageResult<>(data, total, page, size)); }
// MySQL SELECT * FROM user LIMIT ?, ?; -- offset, pageSize // PostgreSQL SELECT * FROM user LIMIT ? OFFSET ?; // SQL Server (2012+) SELECT * FROM user ORDER BY id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY; // Oracle (12c+) SELECT * FROM user OFFSET ? ROWS FETCH NEXT ? ROWS ONLY;
@Data @AllArgsConstructor public class PageResult{ private List data; // 當頁資料 private long total; // 總筆數 private int pageNo; // 當前頁碼 private int pageSize; // 每頁大小 private int totalPages; // 總頁數 // 便利建構子 public PageResult(List data, long total, int pageNo, int pageSize) { this.data = data; this.total = total; this.pageNo = pageNo; this.pageSize = pageSize; this.totalPages = (int) Math.ceil((double) total / pageSize); } // 判斷是否有下一頁 public boolean hasNext() { return pageNo < totalPages; } public boolean hasPrevious() { return pageNo > 1; } }
public interface UserRepository extends JpaRepository{ // 自動支援分頁 Page findByActiveTrue(Pageable pageable); } // Service 使用 Page page = userRepository.findByActiveTrue(PageRequest.of(pageNo - 1, pageSize)); List content = page.getContent(); long total = page.getTotalElements();