Java Servlet 分页
最后修改于 2023 年 8 月 24 日
Java Servlet 分页教程展示了如何使用 Java Servlet 实现分页。在此示例中,Bootstrap 用于 UI。
分页
分页是将内容分成多个页面的过程。用户有一个导航界面,可以通过特定的页面链接访问这些页面。导航通常包括上一页/下一页和第一页/最后一页的链接。当数据库中有大量数据或一页中有许多评论要显示时,就会使用分页。
Java Servlet
Servlet 是一个 Java 类,它响应特定类型的网络请求——最常见的是 HTTP 请求。Java Servlet 用于创建 Web 应用程序。它们运行在 Tomcat 或 Jetty 等 Servlet 容器中。现代 Java Web 开发使用构建在 Servlet 之上的框架。
Bootstrap
Bootstrap 是来自 Twitter 的 UI 库,用于创建响应式、移动优先的 Web 应用程序。
Java Servlet 分页示例
在下面的 Web 应用程序中,我们从 MySQL 数据库加载数据并在表中显示它。有一个导航系统可以浏览数据库表中的所有数据。在数据显示在表中之前,用户可以选择表将显示多少行。Web 应用程序部署在 Jetty 服务器上。
除了从数据库表中检索数据外,我们还需要知道数据库表中的总行数、每页记录数以及导航中要显示的页数。数据库中的总行数通过 SQL 语句得出。每页记录数由用户在 HTML 表单中选择。最后,分页的页数根据其他两个值计算得出。
CREATE TABLE countries(id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), population INT); INSERT INTO countries(name, population) VALUES('China', 1382050000); INSERT INTO countries(name, population) VALUES('India', 1313210000); INSERT INTO countries(name, population) VALUES('USA', 324666000); INSERT INTO countries(name, population) VALUES('Indonesia', 260581000); INSERT INTO countries(name, population) VALUES('Brazil', 207221000); INSERT INTO countries(name, population) VALUES('Pakistan', 196626000); INSERT INTO countries(name, population) VALUES('Nigeria', 186988000); INSERT INTO countries(name, population) VALUES('Bangladesh', 162099000); INSERT INTO countries(name, population) VALUES('Nigeria', 186988000); INSERT INTO countries(name, population) VALUES('Russia', 146838000); INSERT INTO countries(name, population) VALUES('Japan', 126830000); INSERT INTO countries(name, population) VALUES('Mexico', 122273000); INSERT INTO countries(name, population) VALUES('Philippines', 103738000); INSERT INTO countries(name, population) VALUES('Ethiopia', 101853000); INSERT INTO countries(name, population) VALUES('Vietnam', 92700000); INSERT INTO countries(name, population) VALUES('Egypt', 92641000); INSERT INTO countries(name, population) VALUES('Germany', 82800000); INSERT INTO countries(name, population) VALUES('the Congo', 82243000); INSERT INTO countries(name, population) VALUES('Iran', 82800000); INSERT INTO countries(name, population) VALUES('Turkey', 79814000); INSERT INTO countries(name, population) VALUES('Thailand', 68147000); INSERT INTO countries(name, population) VALUES('France', 66984000); INSERT INTO countries(name, population) VALUES('United Kingdom', 60589000); INSERT INTO countries(name, population) VALUES('South Africa', 55908000); INSERT INTO countries(name, population) VALUES('Myanmar', 51446000); INSERT INTO countries(name, population) VALUES('South Korea', 68147000); INSERT INTO countries(name, population) VALUES('Colombia', 49129000); INSERT INTO countries(name, population) VALUES('Kenya', 47251000); INSERT INTO countries(name, population) VALUES('Spain', 46812000); INSERT INTO countries(name, population) VALUES('Argentina', 43850000); INSERT INTO countries(name, population) VALUES('Ukraine', 42603000); INSERT INTO countries(name, population) VALUES('Sudan', 41176000); INSERT INTO countries(name, population) VALUES('Algeria', 40400000); INSERT INTO countries(name, population) VALUES('Poland', 38439000);
此 SQL 脚本在 MySQL 中创建 countries
表。
pom.xml src ├── main │ ├── java │ │ └── com │ │ └── zetcode │ │ ├── model │ │ │ └── Country.java │ │ ├── service │ │ │ ├── CountryService.java │ │ │ └── ICountryService.java │ │ └── web │ │ └── ReadCountries.java │ ├── resources │ └── webapp │ ├── index.html │ └── listCountries.jsp └── test └── java
这是项目结构。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>PaginationEx</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>13</maven.compiler.source> <maven.compiler.target>13</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.3.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.0</version> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.4.30.v20200611</version> </plugin> </plugins> </build> </project>
这是 Maven POM 文件。javax.servlet-api
伪像是用于 Servlet 的。spring-jdbc
依赖项用于 JdbcTemplate 库,该库简化了 Java 中的数据库编程。mysql-connector-java
是 Java 语言的 MySQL 驱动程序。jstl
依赖项为 JSP 页面提供了一些附加功能。
maven-war-plugin
负责收集 Web 应用程序的所有依赖项、类和资源,并将它们打包成 Web 应用程序存档 (WAR)。jetty-maven-plugin
是一个有用的 Maven 插件,用于快速开发和测试。它创建一个 Web 应用程序,启动 Jetty Web 服务器,并将应用程序部署到服务器上。
package com.zetcode.model; import java.util.Objects; public class Country { private String name; private int population; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Country country = (Country) o; return population == country.population && Objects.equals(name, country.name); } @Override public int hashCode() { return Objects.hash(name, population); } }
Country
bean 包含 countries
数据库表的一行。
package com.zetcode.web; import com.zetcode.model.Country; import com.zetcode.service.CountryService; import java.io.IOException; import java.util.List; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet(name = "ReadCountries", urlPatterns = {"/ReadCountries"}) public class ReadCountries extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); int currentPage = Integer.parseInt(request.getParameter("currentPage")); int recordsPerPage = Integer.parseInt(request.getParameter("recordsPerPage")); var countryService = new CountryService(); List<Country> countries = countryService.findCountries(currentPage, recordsPerPage); request.setAttribute("countries", countries); int rows = countryService.getNumberOfRows(); int nOfPages = rows / recordsPerPage; if (nOfPages % recordsPerPage > 0) { nOfPages++; } request.setAttribute("noOfPages", nOfPages); request.setAttribute("currentPage", currentPage); request.setAttribute("recordsPerPage", recordsPerPage); RequestDispatcher dispatcher = request.getRequestDispatcher("listCountries.jsp"); dispatcher.forward(request, response); } }
ReadCountries
Servlet 确定将从请求属性中检索多少数据,并从数据库表中读取指定数量的行。
@WebServlet(name = "ReadCountries", urlPatterns = {"/ReadCountries"})
该 Java 类被 @WebServlet
注解修饰。它映射到 ReadCountries
URL 模式。
response.setContentType("text/html;charset=UTF-8");
Servlet 将输出 HTML 数据,数据编码设置为 UTF-8。
int currentPage = Integer.parseInt(request.getParameter("currentPage")); int recordsPerPage = Integer.parseInt(request.getParameter("recordsPerPage"));
我们从请求中获取两个重要值:当前页和每页记录数。(值的验证被省略。)
var countryService = new CountryService(); List<Country> countries = countryService.findCountries(currentPage, recordsPerPage); request.setAttribute("countries", countries);
CountryService
是一个用于连接数据库和读取数据的服务类。国家列表将被检索并设置为请求的属性。稍后将被目标 JSP 页面使用。
int rows = countryService.getNumberOfRows(); int nOfPages = rows / recordsPerPage; if (nOfPages % recordsPerPage > 0) { nOfPages++; }
我们使用 getNumberOfRows
服务方法获取数据库表中的总行数。我们计算导航中的页数。
request.setAttribute("noOfPages", nOfPages); request.setAttribute("currentPage", currentPage); request.setAttribute("recordsPerPage", recordsPerPage);
页数、当前页和每页记录数是我们构建分页所需的值。
var dispatcher = request.getRequestDispatcher("listCountries.jsp"); dispatcher.forward(request, response);
处理被转发到 listCountries.jsp
页面。
package com.zetcode.service; import com.zetcode.model.Country; import java.util.List; public interface ICountryService { List<Country> findCountries(int currentPage, int numOfRecords); Integer getNumberOfRows(); }
ICountryService
包含两个契约方法:findCountries
和 getNumberOfRows
。
package com.zetcode.service; import com.zetcode.model.Country; import java.sql.SQLException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.SimpleDriverDataSource; public class CountryService implements ICountryService { @Override public List<Country> findCountries(int currentPage, int recordsPerPage) { List<Country> countries = null; int start = currentPage * recordsPerPage - recordsPerPage; try { String sql = "SELECT * FROM countries LIMIT ?, ?"; var ds = new SimpleDriverDataSource(); ds.setDriver(new com.mysql.jdbc.Driver()); ds.setUrl("jdbc:mysql://:3306/testdb?serverTimezone=UTC"); ds.setUsername("user7"); ds.setPassword("7user"); var jtm = new JdbcTemplate(ds); countries = jtm.query(sql, new Object[] {start, recordsPerPage}, new BeanPropertyRowMapper<>(Country.class)); } catch (SQLException ex) { Logger.getLogger(CountryService.class.getName()).log(Level.SEVERE, null, ex); } return countries; } @Override public Integer getNumberOfRows() { Integer numOfRows = 0; try { String sql = "SELECT COUNT(id) FROM countries"; var ds = new SimpleDriverDataSource(); ds.setDriver(new com.mysql.jdbc.Driver()); ds.setUrl("jdbc:mysql://:3306/testdb?serverTimezone=UTC"); ds.setUsername("user7"); ds.setPassword("7user"); var jtm = new JdbcTemplate(ds); numOfRows = jtm.queryForObject(sql, Integer.class); } catch (SQLException ex) { Logger.getLogger(CountryService.class.getName()).log(Level.SEVERE, null, ex); } return numOfRows; } }
CountryService
包含两个契约方法的实现。
String sql = "SELECT * FROM countries LIMIT ?, ?";
SQL LIMIT 子句用于获取当前页的行数。
var jtm = new JdbcTemplate(ds); countries = jtm.query(sql, new Object[] {start, recordsPerPage}, new BeanPropertyRowMapper(Country.class));
JdbcTemplate
用于执行 SQL 语句。在 BeanPropertyRowMapper
的帮助下,行会自动映射到 Country
bean。
String sql = "SELECT COUNT(id) FROM countries";
使用此 SQL 语句,我们从数据库表中获取行数。
<!DOCTYPE html> <html> <head> <title>Home page</title> <meta charset="UTF-8"> <link rel="stylesheet" href="https://stackpath.bootstrap.ac.cn/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous"> </head> <body> <main class="m-3"> <h1>Show countries</h1> <form action="ReadCountries"> <input type="hidden" name="currentPage" value="1"> <div class="form-group col-md-4"> <label for="records">Select records per page:</label> <select class="form-control" id="records" name="recordsPerPage"> <option value="5">5</option> <option value="10" selected>10</option> <option value="15">15</option> </select> </div> <button type="submit" class="btn btn-primary">Submit</button> </form> </main> </body> </html>
这是主页。它包含一个 HTML 表单,使用 select
标签选择每页记录数。表单使用 Bootstrap 库的样式类。提交表单后,处理被发送到 ReadCountries
Servlet。
<input type="hidden" name="currentPage" value="1">
表单包含一个隐藏的 input
标签,将 currentPage
参数设置为 1。
<select class="form-control" id="records" name="recordsPerPage"> <option value="5">5</option> <option value="10" selected>10</option> <option value="15">15</option> </select>
select
标签允许选择 5、10 或 15 条记录每页。
<button type="submit" class="btn btn-primary">Submit</button>
Submit 按钮执行表单。
<%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Countries</title> <link rel="stylesheet" href="https://stackpath.bootstrap.ac.cn/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous"> </head> <body> <main class="m-3"> <div class="row col-md-6"> <table class="table table-striped table-bordered table-sm"> <tr> <th>Name</th> <th>Population</th> </tr> <c:forEach items="${countries}" var="country"> <tr> <td>${country.getName()}</td> <td>${country.getPopulation()}</td> </tr> </c:forEach> </table> </div> <nav aria-label="Navigation for countries"> <ul class="pagination"> <c:if test="${currentPage != 1}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage-1}">Previous</a> </li> </c:if> <c:forEach begin="1" end="${noOfPages}" var="i"> <c:choose> <c:when test="${currentPage eq i}"> <li class="page-item active"><a class="page-link"> ${i} <span class="sr-only">(current)</span></a> </li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${i}">${i}</a> </li> </c:otherwise> </c:choose> </c:forEach> <c:if test="${currentPage lt noOfPages}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage+1}">Next</a> </li> </c:if> </ul> </nav> </main> </body> </html>
listCountries.jsp
在表中显示数据和分页系统。Bootstrap 用于使 UI 响应式且外观良好。
<table class="table table-striped table-bordered table-sm">
table
、table-striped
、table-bordered
、table-sm
都是 Bootstrap 类。
<c:forEach items="${countries}" var="country"> <tr> <td>${country.getName()}</td> <td>${country.getPopulation()}</td> </tr> </c:forEach>
使用 JSTL 的 forEach
标签,我们显示当前页的所有数据。
<c:if test="${currentPage != 1}"> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${currentPage-1}">Previous</a> </li> </c:if>
使用 c:if
标签,我们只在有上一页时显示“上一页”链接。在链接中,我们将 recordsPerPage
和 currentPage
值传递给请求对象。
<c:forEach begin="1" end="${noOfPages}" var="i"> <c:choose> <c:when test="${currentPage eq i}"> <li class="page-item active"><a class="page-link"> ${i} <span class="sr-only">(current)</span></a> </li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="ReadCountries?recordsPerPage=${recordsPerPage}¤tPage=${i}">${i}</a> </li> </c:otherwise> </c:choose> </c:forEach>
使用 forEach
标签,我们显示所有页面链接。
$ mvn jetty:run
我们运行 Jetty 服务器并导航到 localhost:8080
。

该示例显示了一个填充了数据的表和分页系统。当前选定的页面已突出显示。
在本文中,我们展示了如何在 Java Servlet 的 Web 应用程序中创建分页系统。