| 類型 | 原理 | 優點 | 缺點 |
|---|---|---|---|
| 物理分頁 | SQL 層面僅查詢當前頁數據(LIMIT/ROWNUM/TOP) | 內存佔用極低,適合大數據量 | 需資料庫支援,跨資料庫需適配 |
| 邏輯分頁 | 一次性查全表載入內存,再在記憶體切頁 | 查詢簡單,後續翻頁極快 | 內存壓力大,不適合大數據 |
生產環境 強烈推薦物理分頁。
-- MySQL / PostgreSQL / SQLite
SELECT * FROM t LIMIT offset, pageSize;
-- SQL Server 2012+
SELECT * FROM t ORDER BY id OFFSET offset ROWS FETCH NEXT pageSize ROWS ONLY;
-- Oracle 12c+
SELECT * FROM t OFFSET offset ROWS FETCH NEXT pageSize ROWS ONLY;
-- Oracle 11g 經典寫法
SELECT * FROM (SELECT a.*, ROWNUM rn FROM (SELECT * FROM t) a WHERE ROWNUM <= offset+pageSize) WHERE rn > offset;
public class User {
private Long id;
private String username;
private String email;
// getter/setter 省略
}
<mapper namespace="com.example.dao.UserMapper">
<!-- 物理分頁查詢:#{page.start} 對應 Page 物件的 start 屬性 -->
<select id="selectPage" resultType="com.example.entity.User">
SELECT id, username, email
FROM sys_user
<where>
<if test="param.keyword != null and param.keyword != ''">
AND username LIKE CONCAT('%', #{param.keyword}, '%')
</if>
</where>
ORDER BY id DESC
LIMIT #{page.start}, #{page.pageSize}
</select>
<!-- 總條數查詢(不分頁) -->
<select id="selectCount" resultType="long">
SELECT COUNT(1) FROM sys_user
<where>
<if test="keyword != null and keyword != ''">
AND username LIKE CONCAT('%', #{keyword}, '%')
</if>
</where>
</select>
</mapper>
public class Page<T> implements Serializable {
private int pageNum = 1; // 當前頁(1-based)
private int pageSize = 10; // 每頁條數
private long total; // 總條數
private List<T> records; // 當前頁數據
// 計算屬性
public int getStart() { return (pageNum - 1) * pageSize; }
public long getPages() { return (total + pageSize - 1) / pageSize; }
public boolean hasPrevious() { return pageNum > 1; }
public boolean hasNext() { return pageNum < getPages(); }
// getter/setter 省略
}
@Service
public class UserServiceImpl implements UserService {
@Autowired UserMapper userMapper;
@Override
public Page<User> queryPage(int pageNum, int pageSize, String keyword) {
Page<User> page = new Page<>();
page.setPageNum(pageNum);
page.setPageSize(pageSize);
// 1. 查總數
long total = userMapper.selectCount(keyword);
page.setTotal(total);
// 2. 查當前頁數據
Map<String, Object> param = new HashMap<>();
param.put("page", page);
param.put("keyword", keyword);
List<User> records = userMapper.selectPage(param);
page.setRecords(records);
return page;
}
}
@GetMapping("/users")
public Result<Page<User>> list(
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
@RequestParam(required = false) String keyword) {
return Result.ok(userService.queryPage(pageNum, pageSize, keyword));
}
| 插件 | 特點 | 適用場景 |
|---|---|---|
| PageHelper | 無侵入、支援多種 DB、自動攔截 RowBounds | 快速接入舊專案、團隊熟悉 MyBatis |
| MyBatis-Plus | 內建 IPage、條件構造器、代碼生成器 | 新專案、追求開發效率 |
| 手寫 | 完全可控、零依賴、適合學習原理 | 特殊分頁邏輯、極簡依賴要求 |
手寫物理分頁核心在於 「總數查詢 + LIMIT 查詢」 兩條 SQL,配合 Page 物件封裝 start/pageSize。理解此原理後,無論使用 PageHelper 還是 MyBatis-Plus,遇到複雜分頁(如多表關聯、動態排序)也能游刃有餘地定製 SQL。