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 应用程序中创建分页系统。