JSP分頁顯示資料
最近在做一個小程式,用到了JSP的分頁。雖然只是最簡單的分頁,但是還是花了我不少時間。這看似簡單的功能,實現起來還是稍微有點麻煩。實現分頁功能,需要知道資料的總個數,每頁應該有多少條資料,以及當前頁碼。假如總共有300條資料,每頁20條,那麼應該就有15頁;假設有301條資料,每頁20條,這時候就需要16頁。因此,總頁數可以這樣計算:總頁數=資料總數%每頁條數==0?資料總數/每頁條數:資料總數/每頁條數+1
。為了能顯示當前頁的資料,我們需要知道當前頁碼,然後根據當前頁碼計算應該顯示哪些資料。因此,我們還需要一個引數來跟蹤當前頁碼。
知道了這些,就可以開始分頁的實現了。
簡單分頁
首先來看看最簡單的分頁。我們先不考慮資料庫如何分頁,假設現在我們直接獲取到了所有資料,只考慮如何將這些資料分頁。
後端程式碼
首先我們需要一個實體類,其他方法已省略。
public class User {
private int id;
private String name;
private String password;
private LocalDate birthday;
}
然後需要一個數據訪問層的介面:
public interface UserRepository {
List<User> listAll();
}
然後我們來實現這個介面,作為我們的資料來源。
public class MemoryUserRepository implements UserRepository {
public static final int COUNTS = 302;
@Override
public List<User> listAll() {
List<User> users = new ArrayList<>();
for (int i = 0; i < COUNTS; ++i) {
User user = new User();
user.setId(i + 1);
user. setName("使用者" + i + 1);
user.setPassword("12345" + i);
user.setBirthday(LocalDate.now());
users.add(user);
}
return users;
}
}
然後我們需要一個Servlet,來計算總頁數等這些分頁相關的變數,然後將分頁資訊傳遞給JSP。這個分頁非常簡單,實際上是利用了List介面的subList方法來切分資料,而這個方法需要接受子列的起始索引和結束索引組成的閉開區間,所以我們需要計算本頁起始使用者序號和本頁末尾使用者序號的下一個。如果資料有零頭,不夠一整頁,那麼我們就需要判斷一下末尾序號是否超過了列表的大小。
@WebServlet(name = "ListAllServlet", urlPatterns = {"/list"})
public class ListAllServlet extends HttpServlet {
private List<User> users;
private UserRepository repository;
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String p = req.getParameter("page");
int page;
try {
//當前頁數
page = Integer.valueOf(p);
} catch (NumberFormatException e) {
page = 1;
}
//使用者總數
int totalUsers = users.size();
//每頁使用者數
int usersPerPage = 20;
//總頁數
int totalPages = totalUsers % usersPerPage == 0 ? totalUsers / usersPerPage : totalUsers / usersPerPage + 1;
//本頁起始使用者序號
int beginIndex = (page - 1) * usersPerPage;
//本頁末尾使用者序號的下一個
int endIndex = beginIndex + usersPerPage;
if (endIndex > totalUsers)
endIndex = totalUsers;
req.setAttribute("totalUsers", totalUsers);
req.setAttribute("usersPerPage", usersPerPage);
req.setAttribute("totalPages", totalPages);
req.setAttribute("beginIndex", beginIndex);
req.setAttribute("endIndex", endIndex);
req.setAttribute("page", page);
req.setAttribute("users", users);
req.getRequestDispatcher("list.jsp").forward(req, resp);
}
@Override
public void init() throws ServletException {
repository = new MemoryUserRepository();
users = repository.listAll();
}
}
上面這個Servlet中的list.jsp
就是我們具體顯示的頁面了。下面我們要做的就是處理前端了。
前端程式碼
分頁元件
首先來看看前端如何分頁。我在這裡用的前端框架是Bootstrap,它也提供了一個分頁元件pagination
,只需要在頁面中新增如下一段程式碼。
<nav>
<ul class="pagination">
<li><a href="#">«</a></li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">»</a></li>
</ul>
</nav>
當然,這段程式碼是靜態的,我們要讓它產生動態的行為,就需要放到JSP中進行處理。
JSP程式碼
下面是我的JSP程式碼。我用了JSTL來做JSP的擴充套件,因此在專案中還需要新增JSTL的包。為了簡潔,我將一些不相關的程式碼寫在了其它JSP中,然後包含進來。_header.jsp
是引入Bootstrap的一些程式碼。_navbar.jsp
和_footer.jsp
則是可選的導航條和頁尾,沒有也罷。
然後是一堆<c:set>
,設定了我們分頁要使用的一些變數。currentPageUsers
這個變數做了實際的分頁工作。
然後,我用了一個表格來顯示當前頁的資料。用到了JSTL的<c:forEach>
標籤。
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@page pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>簡單分頁</title>
<%@include file="_header.jsp" %>
</head>
<body>
<%@include file="_navbar.jsp" %>
<div class="container">
<c:set var="totalUsers" value="${requestScope.totalUsers}"/>
<c:set var="usersPerPage" value="${requestScope.usersPerPage}"/>
<c:set var="totalPages" value="${requestScope.totalPages}"/>
<c:set var="beginIndex" value="${requestScope.beginIndex}"/>
<c:set var="endIndex" value="${requestScope.endIndex}"/>
<c:set var="page" value="${requestScope.page}"/>
<c:set var="currentPageUsers" value="${requestScope.users.subList(beginIndex,endIndex)}"/>
<p>使用者總數:${totalUsers}</p>
<p>每頁使用者數:${usersPerPage}</p>
<p>總頁數:${totalPages}</p>
<p>當前頁:${page}</p>
<table class="table table-hover table-responsive table-striped table-bordered">
<thead>
<tr>
<td>使用者編號</td>
<td>姓名</td>
<td>密碼</td>
<td>生日</td>
</tr>
</thead>
<tbody>
<c:forEach var="user" items="${currentPageUsers}">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.password}</td>
<td>${user.birthday}</td>
</tr>
</c:forEach>
</tbody>
</table>
<hr>
<div class="text-center">
<nav>
<ul class="pagination">
<li><a href="<c:url value="/list?page=1"/>">首頁</a></li>
<li><a href="<c:url value="/list?page=${page-1>1?page-1:1}"/>">«</a></li>
<c:forEach begin="1" end="${totalPages}" varStatus="loop">
<c:set var="active" value="${loop.index==page?'active':''}"/>
<li class="${active}"><a
href="<c:url value="/list?page=${loop.index}"/>">${loop.index}</a>
</li>
</c:forEach>
<li>
<a href="<c:url value="/list?page=${page+1<totalPages?page+1:totalPages}"/>">»</a>
</li>
<li><a href="<c:url value="/list?page=${totalPages}"/>">尾頁</a></li>
</ul>
</nav>
</div>
</div>
<%@include file="_footer.jsp" %>
</body>
</html>
表格最後就是我們動態化的Bootstrap分頁。首頁、尾頁、上一頁、下一頁都是固定的,不管有多少頁都必須顯示的。然後又用了一個<c:forEach>
標籤迴圈列出所有頁。如果某頁和當前頁頁碼相同,還為這頁添加了active
類,讓其高亮顯示。這些分頁連結最後需要跟一個page引數,表明要檢視的是哪一頁。
最後的顯示效果如下:
資料庫分頁
上面僅僅使用一個列表簡單演示了最基本的分頁。下面來看看資料庫分頁。大部分資料庫都支援結果的分頁。這裡我用MySQL資料庫,它支援如下的分頁語句:SELECT * FROM 表名 LIMIT m, n
,m是起始資料,n是偏移量。假如我們要前20條資料,就需要SELECT * FROM 表名 LIMIT 0, 20
,如果我們需要第二頁(21-40條),就需要SELECT * FROM 表名 LIMIT 20, 20
。
建立資料庫
確定資料庫分頁方式之後,我們就可以實現資料庫分頁了。首先需要一個數據庫表。我還定義了兩個儲存過程,一個儲存過程用於新增初始資料,另一個儲存過程用於獲取使用者總數。
DROP DATABASE IF EXISTS page;
CREATE DATABASE page;
USE page;
CREATE TABLE user (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
birthday DATETIME
);
DELIMITER //
CREATE PROCEDURE init_user(IN count INT)
BEGIN
DECLARE i INT;
SET i = 1;
WHILE i <= count DO
INSERT INTO user (name, password, birthday) VALUES (concat('使用者', i), concat('123456', i), now());
SET i = i + 1;
END WHILE;
END//
DELIMITER ;
CALL init_user(202);
DELIMITER //
CREATE PROCEDURE user_counts(OUT count INT)
BEGIN
SELECT count(id)
INTO count
FROM user;
END//
DELIMITER ;
後端程式碼
我們需要一個支援分頁的介面:
public interface PageableUserRepository extends UserRepository {
List<User> listAllOf(int startIndex, int offset);
int counts();
}
相應的需要一個數據訪問層的實現。由於用到了儲存過程,這裡還使用了JDBC的CallableStatement來呼叫儲存過程。
public class DataBaseUserRepository implements PageableUserRepository {
private Connection connection;
private static final String url = "jdbc:mysql://localhost:3306/page";
private static final String username = "root";
private static final String password = "12345678";
public DataBaseUserRepository() {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public List<User> listAll() {
List<User> users = new ArrayList<>();
try (PreparedStatement statement = connection.prepareStatement("SELECT id,name,password,birthday FROM user ")) {
ResultSet rs = statement.executeQuery();
while (rs.next()) {
User user = new User();
user.setId(rs.getInt(1));
user.setName(rs.getString(2));
user.setPassword(rs.getString(3));
user.setBirthday(rs.getObject(4, LocalDate.class));
users.add(user);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return users;
}
@Override
public List<User> listAllOf(int startIndex, int offset) {
List<User> users = new ArrayList<>();
try (PreparedStatement statement = connection.prepareStatement("SELECT id,name,password,birthday FROM user LIMIT ?,?")) {
statement.setInt(1, startIndex);
statement.setInt(2, offset);
ResultSet rs = statement.executeQuery();
while (rs.next()) {
User user = new User();
user.setId(rs.getInt(1));
user.setName(rs.getString(2));
user.setPassword(rs.getString(3