在 JavaWeb 开发中,当需要展示大量数据(如 10 万条用户记录、百万级订单列表)时,“一次性加载所有数据” 会导致两个严重问题:服务器查询耗时过长(数据库压力大)、浏览器渲染卡顿(前端加载缓慢)。而分页技术正是解决这个问题的 “标准方案”——将数据分成多页,用户每次只加载当前页数据,平衡性能与体验。
本文专为 JavaWeb 开发者设计,从底层原理到完整代码实现,手把手教你用 3 步实现分页功能,涵盖 MySQL 分页查询、后端参数计算、前端页码渲染全流程。
一、先搞懂:分页的核心原理是什么?
分页本质是 “分批次查询 + 按需展示”,核心由 3 部分组成:前端参数传递、后端逻辑处理、数据库分页查询,三者协作流程如下:
- 前端传递参数:用户点击 “第 2 页” 时,前端向服务器传递
当前页码(pageNum)和每页条数(pageSize); - 后端计算关键参数:服务器根据这两个参数,计算
跳过的条数(start)和总页数(totalPage); - 数据库分页查询:用
start和pageSize查询当前页数据,同时查询总条数(totalCount); - 返回结果渲染:后端将 “当前页数据 + 分页参数” 返回给前端,前端渲染数据和页码控件。
核心参数公式(新手必记)
| 参数名称 | 含义 | 计算公式 / 说明 |
|---|---|---|
| pageNum | 当前页码 | 前端传递,默认值为 1(避免空指针) |
| pageSize | 每页显示条数 | 前端传递或后端固定,常见值:10、20、50 |
| start | 跳过的条数(分页起点) | (pageNum - 1) * pageSize |
| totalCount | 总数据条数 | 数据库查询(SELECT COUNT(*) FROM 表) |
| totalPage | 总页数 | totalCount % pageSize == 0 ? totalCount/pageSize : totalCount/pageSize + 1 |
例如:总条数 123,每页 10 条 → 总页数 13(123=12*10+3);当前页 2 → 跳过 10 条(从第 11 条开始查)。
二、实战:3 步实现分页功能(Servlet+JSP+MySQL)
以 “用户列表分页” 为例,完整实现从后端查询到前端展示的全流程,新手可直接复用代码。
步骤 1:准备工作(数据库表 + JavaBean)
(1)创建用户表(MySQL)
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`age` int(3) DEFAULT NULL,
`address` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 插入测试数据(100条,模拟大量数据)
INSERT INTO `user` (username, age, address) VALUES
('张三', 22, '北京'), ('李四', 25, '上海'),
-- ... 省略其他98条数据 ...
(2)创建 User 实体类(JavaBean)
public class User {
private Integer id;
private String username;
private Integer age;
private String address;
// 构造器、getter、setter(必须有,否则JSP无法获取属性)
public User() {}
public User(Integer id, String username, Integer age, String address) {
this.id = id;
this.username = username;
this.age = age;
this.address = address;
}
// getter和setter省略(IDEA可自动生成)
}
(3)创建分页工具类(封装分页参数)
为避免参数零散,用一个类封装所有分页相关数据:
public class PageBean<T> {
private int pageNum; // 当前页码
private int pageSize; // 每页条数
private int totalCount; // 总条数
private int totalPage; // 总页数
private List<T> dataList; // 当前页数据
// 计算总页数(setTotalCount时自动计算)
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
this.totalPage = (totalCount % pageSize == 0) ?
totalCount / pageSize : totalCount / pageSize + 1;
}
// getter和setter省略
}
步骤 2:后端处理(Servlet + 数据库查询)
(1)编写 DAO 层(数据库操作)
用 JDBC 实现分页查询和总条数查询(实际项目可用 MyBatis 简化):
public class UserDao {
// 分页查询当前页数据
public List<User> findByPage(int start, int pageSize) {
List<User> userList = new ArrayList<>();
String sql = "SELECT id, username, age, address FROM user LIMIT ?, ?";
try (Connection conn = JDBCUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, start);
pstmt.setInt(2, pageSize);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
User user = new User(
rs.getInt("id"),
rs.getString("username"),
rs.getInt("age"),
rs.getString("address")
);
userList.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return userList;
}
// 查询总条数
public int findTotalCount() {
String sql = "SELECT COUNT(*) FROM user";
try (Connection conn = JDBCUtils.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
return rs.getInt(1); // 返回总条数
}
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
}
(2)编写 Servlet(处理请求,计算参数)
@WebServlet("/user/pageList")
public class UserPageServlet extends HttpServlet {
private UserDao userDao = new UserDao();
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 获取前端参数(pageNum和pageSize),设置默认值
String pageNumStr = request.getParameter("pageNum");
String pageSizeStr = request.getParameter("pageSize");
int pageNum = (pageNumStr == null || "".equals(pageNumStr)) ? 1 : Integer.parseInt(pageNumStr);
int pageSize = (pageSizeStr == null || "".equals(pageSizeStr)) ? 10 : Integer.parseInt(pageSizeStr);
// 2. 校验参数(防止pageNum <=0 或过大)
if (pageNum < 1) pageNum = 1;
// 3. 计算分页参数
int start = (pageNum - 1) * pageSize;
int totalCount = userDao.findTotalCount(); // 查总条数
int totalPage = (totalCount % pageSize == 0) ? totalCount/pageSize : totalCount/pageSize + 1;
// 再次校验pageNum(如果超过总页数,设为最后一页)
if (pageNum > totalPage && totalPage > 0) pageNum = totalPage;
// 4. 查询当前页数据
List<User> userList = userDao.findByPage(start, pageSize);
// 5. 封装到PageBean
PageBean<User> pageBean = new PageBean<>();
pageBean.setPageNum(pageNum);
pageBean.setPageSize(pageSize);
pageBean.setTotalCount(totalCount);
pageBean.setTotalPage(totalPage);
pageBean.setDataList(userList);
// 6. 传递到JSP
request.setAttribute("pageBean", pageBean);
request.getRequestDispatcher("/user/pageList.jsp").forward(request, response);
}
}
步骤 3:前端渲染(JSP 展示数据和页码)
在web/user目录下创建pageList.jsp,实现数据表格和分页控件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.example.pojo.PageBean,com.example.pojo.User,java.util.List" %>
<html>
<head>
<title>用户列表分页</title>
<style>
table { border-collapse: collapse; width: 80%; margin: 20px auto; }
th, td { border: 1px solid #333; padding: 12px; text-align: center; }
.pagination { text-align: center; margin: 20px; }
.pagination a {
display: inline-block;
padding: 6px 12px;
margin: 0 5px;
border: 1px solid #2f54eb;
color: #2f54eb;
text-decoration: none;
}
.pagination a.active {
background: #2f54eb;
color: white;
border-color: #2f54eb;
}
.pagination a:hover { border-color: #1d39c4; color: #1d39c4; }
</style>
</head>
<body>
<h1 style="text-align: center;">用户列表(分页展示)</h1>
<table>
<tr>
<th>ID</th>
<th>姓名</th>
<th>年龄</th>
<th>地址</th>
</tr>
<%
PageBean<User> pageBean = (PageBean<User>) request.getAttribute("pageBean");
List<User> userList = pageBean.getDataList();
if (userList != null && !userList.isEmpty()) {
for (User user : userList) {
%>
<tr>
<td><%= user.getId() %></td>
<td><%= user.getUsername() %></td>
<td><%= user.getAge() %></td>
<td><%= user.getAddress() %></td>
</tr>
<%
}
} else {
%>
<tr>
<td colspan="4">暂无数据</td>
</tr>
<% } %>
</table>
<%-- 分页控件 --%>
<div class="pagination">
<%
int pageNum = pageBean.getPageNum();
int totalPage = pageBean.getTotalPage();
int pageSize = pageBean.getPageSize();
int totalCount = pageBean.getTotalCount();
%>
<p>共<%= totalCount %>条数据,每页<%= pageSize %>条,共<%= totalPage %>页</p>
<%-- 首页和上一页(如果是第1页,不显示) --%>
<% if (pageNum > 1) { %>
<a href="/JavaWebDemo/user/pageList?pageNum=1&pageSize=<%= pageSize %>">首页</a>
<a href="/JavaWebDemo/user/pageList?pageNum=<%= pageNum-1 %>&pageSize=<%= pageSize %>">上一页</a>
<% } %>
<%-- 页码列表(显示当前页前后2页,避免页码过多) --%>
<%
// 计算起始页码(避免小于1)
int startPage = Math.max(1, pageNum - 2);
// 计算结束页码(避免大于总页数)
int endPage = Math.min(totalPage, pageNum + 2);
for (int i = startPage; i <= endPage; i++) {
%>
<a href="/JavaWebDemo/user/pageList?pageNum=<%= i %>&pageSize=<%= pageSize %>"
class="<%= i == pageNum ? "active" : "" %>">
<%= i %>
</a>
<% } %>
<%-- 下一页和末页(如果是最后一页,不显示) --%>
<% if (pageNum < totalPage) { %>
<a href="/JavaWebDemo/user/pageList?pageNum=<%= pageNum+1 %>&pageSize=<%= pageSize %>">下一页</a>
<a href="/JavaWebDemo/user/pageList?pageNum=<%= totalPage %>&pageSize=<%= pageSize %>">末页</a>
<% } %>
</div>
</body>
</html>
三、关键优化:让分页更高效、体验更好
基础分页实现后,还需注意 3 个优化点,避免性能问题和用户体验缺陷:
1. 性能优化:减少不必要的总条数查询
总条数查询(SELECT COUNT(*))在数据量大时可能耗时,可做以下优化:
- 缓存总条数:非实时性场景(如商品列表),将总条数缓存到 Redis,定时更新;
- 延迟查询:首次加载时只查当前页数据,用户点击 “下一页” 再查总条数(适合数据量极大的场景);
- 优化 COUNT 语句:MySQL 中
COUNT(*)比COUNT(1)或COUNT(id)更高效(MySQL 对COUNT(*)有优化)。
2. 体验优化:页码控件动态适配
- 限制页码显示数量:当总页数超过 10 页时,只显示当前页前后 2-3 页(如示例中
startPage和endPage的计算),避免页码过多导致控件过长; - 支持自定义每页条数:前端增加下拉框(10 条 / 页、20 条 / 页、50 条 / 页),让用户按需选择;
- 添加加载状态:用户点击页码时,显示 “加载中” 提示,避免重复点击。
3. 安全优化:防止参数篡改和 SQL 注入
- 参数校验:后端必须校验
pageNum和pageSize(如pageNum不能小于 1,pageSize不能超过 1000,防止恶意请求大量数据); - 用预编译语句:数据库查询时必须用
PreparedStatement(如示例中LIMIT ?, ?),避免直接拼接 SQL 字符串导致注入攻击。
四、新手避坑:5 个高频问题及解决方案
-
坑 1:分页参数为 null 导致空指针异常
原因:前端未传递pageNum或pageSize,后端直接Integer.parseInt()。
✅ 解决方案:设置默认值,如int pageNum = (pageNumStr == null) ? 1 : Integer.parseInt(pageNumStr)。 -
坑 2:页码超出总页数导致无数据
原因:用户手动输入pageNum=1000(超过总页数),查询结果为空。
✅ 解决方案:后端校验,若pageNum > totalPage,强制设为totalPage(如示例中代码)。 -
坑 3:LIMIT 参数错误导致查询失败
原因:start计算错误(如pageNum=1时start=1*pageSize,导致跳过第 1 页数据)。
✅ 解决方案:牢记start = (pageNum - 1) * pageSize(pageNum=1时start=0,从第 1 条开始查)。 -
坑 4:总页数计算错误(少一页或多一页)
原因:用totalCount / pageSize直接计算,未处理余数。
✅ 解决方案:用三目运算符:totalPage = totalCount % pageSize == 0 ? totalCount/pageSize : totalCount/pageSize + 1。 -
坑 5:前端页码点击后样式不变
原因:未给当前页添加高亮样式,用户分不清当前在第几页。
✅ 解决方案:在 JSP 中判断i == pageNum时,添加active类(如示例中class="<%= i == pageNum ? "active" : "" %>")。
总结
分页的核心是 “分批次查询 + 参数计算”,掌握(pageNum-1)*pageSize的分页起点计算、LIMIT语句的使用、前端页码控件的渲染,就能实现基础分页功能。在此基础上,通过缓存总条数、优化页码显示、校验参数等方式,可进一步提升性能和体验。
如果这篇文章帮你解决了分页开发的困惑,欢迎点赞、收藏,评论区可以分享你的实现技巧或遇到的问题~



3078

被折叠的 条评论
为什么被折叠?



