當資料量增長至數千甚至上萬筆時,一次性載入所有記錄會導致記憶體溢出、頁面渲染緩慢、用戶體驗極差。分頁查詢透過限制每次返回的記錄數,配合頁碼導航,成為 Web 應用處理大量資料的標準模式。本文以 JSP + MySQL 為例,詳細解析基於 JDBC 的分頁實現邏輯、代碼結構與效能優化方向。
傳統 JDBC 分頁採用「游標定位 + 限制筆數」策略:
SELECT COUNT(*) 獲取總記錄數 totalRows。totalPages = (totalRows + pageSize - 1) / pageSize。currentPage 計算起始位置:offset = (currentPage - 1) * pageSize。ResultSet.TYPE_SCROLL_INSENSITIVE 建立可捲動結果集,調用 absolute(offset + 1) 定位游標,再循環讀取 pageSize 條記錄。<%
int pageSize = 10; // 每頁顯示筆數
String pageParam = request.getParameter("page");
int currentPage = 1;
if (pageParam != null) {
try { currentPage = Integer.parseInt(pageParam); }
catch (NumberFormatException ignored) {}
}
if (currentPage < 1) currentPage = 1;
%>
<%
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTC",
"user", "password");
// 使用預編譯語句防止 SQL 注入
PreparedStatement countStmt = conn.prepareStatement(
"SELECT COUNT(*) FROM dxswlxh");
ResultSet countRs = countStmt.executeQuery();
countRs.next();
int totalRows = countRs.getInt(1);
countRs.close(); countStmt.close();
int totalPages = (totalRows + pageSize - 1) / pageSize;
if (currentPage > totalPages) currentPage = totalPages;
%>
<%
// MySQL 專用 LIMIT 語法(效能優於游標定位)
String sql = "SELECT id, name, description FROM dxswlxh LIMIT ?, ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, (currentPage - 1) * pageSize); // offset
pstmt.setInt(2, pageSize); // limit
ResultSet rs = pstmt.executeQuery();
%>
<table border="1" cellpadding="5" cellspacing="0">
<tr><th>ID</th><th>名稱</th><th>描述</th></tr>
<% while (rs.next()) { %>
<tr>
<td><%= rs.getInt("id") %></td>
<td><%= rs.getString("name") %></td>
<td><%= rs.getString("description") %></td>
</tr>
<% } %>
</table>
<% rs.close(); pstmt.close(); conn.close(); %>
<div class="pagination">
<a href="?page=1">首頁</a>
<% if (currentPage > 1) { %>
<a href="?page=<%= currentPage - 1 %>">上一頁</a>
<% } %>
<%
int start = Math.max(1, currentPage - 2);
int end = Math.min(totalPages, currentPage + 2);
for (int i = start; i <= end; i++) { %>
<a href="?page=<%= i %>" class="<%= i==currentPage?'active':'' %>"><%= i %></a>
<% } %>
<% if (currentPage < totalPages) { %>
<a href="?page=<%= currentPage + 1 %>">下一頁</a>
<% } %>
<a href="?page=<%= totalPages %>">尾頁</a>
<span>第 <%= currentPage %> / <%= totalPages %> 頁,共 <%= totalRows %> 條</span>
</div>
原代碼使用 ResultSet.TYPE_SCROLL_INSENSITIVE 配合 absolute() 定位,此方式會將所有結果集載入記憶體,大數據量下極易導致 OOM。強烈建議改用數據庫原生分頁語法:
LIMIT offset, pageSizeOFFSET offset ROWS FETCH NEXT pageSize ROWS ONLYOFFSET offset ROWS FETCH NEXT pageSize ROWS ONLY生產環境必須使用連接池(HikariCP、Druid、DBCP2),並利用 try-with-resources 確保連接、語句、結果集正確關閉:
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 處理結果集
} // 自動關閉資源
當 offset 極大時(如第 10000 頁),LIMIT 100000, 10 仍需掃描前 10 萬行。解決方案:
WHERE id > lastMaxId ORDER BY id LIMIT pageSize。將分頁邏輯下沉至 DAO/Repository 層,JSP 僅負責渲染。引入分頁DTO對象封裝參數與結果:
public class PageResult<T> {
private List<T> records;
private long total;
private int pageNum, pageSize;
// getter/setter...
}
配合 MyBatis Plus、Spring Data JPA 等框架可進一步簡化分頁代碼,專注業務邏輯開發。
JSP + MySQL 分頁查詢的核心在於「總數統計 + 限制查詢 + 頁碼導航」三步驟。雖然傳統 Scriptlet 寫法直觀,但存在業務邏輯與視圖耦合、資源洩漏風險、效能瓶頸等問題。現代開發應採用 MVC 分層、連接池、原生分頁語法、框架支撐,構建高效、可維護的分頁功能。