【Java面試題】之分頁功能的實現

以下內容是根據網上內容以及傳智播客教學整理而來,侵刪。

百度知道網友@starstroll 的回答:

分頁的實現可分為兩大類:一、資料在Java程式碼中進行分頁,然後取得當前頁資料;二、在資料庫中直接取得當前頁資料。
通常面試官都希望聽到後者,因為那才是高效的方法。你如果想讓面試官覺得你的能力高的話你就先否定他的問題,你可以回答說:“Java中根本不需要做分頁的實現程式碼只管拿資料庫中的當前頁資料即可,資料分頁功能應該交由SQL處理,在分頁實現中Java最多隻實現總頁數的計算,除此以外幾乎不用管。”如果你這麼答的話面試官通常會問你總頁數的演算法,至於這個你可以網上找個高效點的方法,我現在知道最高效的就是:(資料總行數 每頁資料行數-1)/每頁資料行數。
演算法可能有更高效的,你可以到網上找找。記住只在面試中才能這麼答,筆試的話老老實實寫出實現方法。否定面試官的問題會讓他覺得你更professional,但不要太囂張不然適得其反的。

本文中的分頁基於Mysql實現,使用limit關鍵字。Oracle中沒有limit,使用的是三層巢狀。關鍵是理解分頁的實現,原理都是一樣的

不使用任何框架,採用JSP servlet實現

先介紹一下Mysql中Limit關鍵字

LIMIT 子句可以被用於強制 SELECT 語句返回指定的記錄數。LIMIT 接受一個或兩個數字引數。引數必須是一個整數常量。

如果給定兩個引數,第一個引數指定第一個返回記錄行的偏移量,第二個引數指定返回記錄行的最大數目。

初始記錄行的偏移量是 0(而不是 1):

  mysql> SELECT * FROM table LIMIT 5,10; // 檢索記錄行 6-15
  為了檢索從某一個偏移量到記錄集的結束所有的記錄行,可以指定第二個引數為 -1:
  mysql> SELECT * FROM table LIMIT 95,-1; // 檢索記錄行 96-last.
  如果只給定一個引數,它表示返回最大的記錄行數目:
  mysql> SELECT * FROM table LIMIT 5; //檢索前 5 個記錄行
  換句話說,LIMIT n 等價於 LIMIT 0,n。

總結:查詢語句應該為(最關鍵的優兩個引數,每頁記錄數和當前頁)

SELECT * FROM 表 LIMIT (當前頁-1)*每頁顯示記錄數,每頁顯示的記錄數

原理分析圖如下:(核心是分頁的JavaBean設計)

實現流程

0.      環境準備

a)        引入jar檔案及引入配置檔案

                                      i.             資料庫驅動包

                                    ii.             C3P0連線池jar檔案 及 配置檔案

                                   iii.             DbUtis元件:    QueryRunner qr = new QueryRuner(dataSouce);

qr.update(sql);

b)        公用類: JdbcUtils.java

1.      先設計:PageBean.java

2.      Dao介面設計/實現:   2個方法

3.      Service/servlet

4.      JSP

程式碼實現:

1.C3P0配置檔案 c3p0-config.xml(放在src目錄下)


<c3p0-config>
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo
</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">3</property>
<property name="maxPoolSize">6</property>
<property name="maxIdleTime">1000</property>
</default-config>
<named-config name="oracle_config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_demo</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">3</property>
<property name="maxPoolSize">6</property>
<property name="maxIdleTime">1000</property>
</named-config>
</c3p0-config>

2.JdbcUtil工具類

package cn.itcast.utils;
import javax.sql.DataSource;
import org.apache.commons.dbutils.QueryRunner;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* 工具類
* 1. 初始化C3P0連線池
* 2. 建立DbUtils核心工具類物件
* @author Jie.Yuan
*
*/
public class JdbcUtils {
/**
*  1. 初始化C3P0連線池
*/
private static  DataSource dataSource;
static {
dataSource = new ComboPooledDataSource();
}
/**
* 2. 建立DbUtils核心工具類物件
*/
public static QueryRunner getQueryRuner(){
// 建立QueryRunner物件,傳入連線池物件
// 在建立QueryRunner物件的時候,如果傳入了資料來源物件;
// 那麼在使用QueryRunner物件方法的時候,就不需要傳入連線物件;
// 會自動從資料來源中獲取連線(不用關閉連線)
return new QueryRunner(dataSource);
}
}

3.核心類:PageBean.java

 

package cn.itcast.utils;
import java.util.List;
import cn.itcast.entity.Employee;
/**
* 封裝分頁的引數
* 
* @author Jie.Yuan
* 
*/
public class PageBean<T> {
private int currentPage = 1; // 當前頁, 預設顯示第一頁
private int pageCount = 4;   // 每頁顯示的行數(查詢返回的行數), 預設每頁顯示4行
private int totalCount;      // 總記錄數
private int totalPage;       // 總頁數 = 總記錄數 / 每頁顯示的行數  (  1)
private List<T> pageData;       // 分頁查詢到的資料
// 返回總頁數
public int getTotalPage() {
if (totalCount % pageCount == 0) {
totalPage = totalCount / pageCount;
} else {
totalPage = totalCount / pageCount   1;
}
return totalPage;
}
//......省略getter和setter方法
}

  4.實體類設計 Employee.java(與資料庫中的欄位一一對應)

package cn.itcast.entity;
/**
* 1. 實體類設計 (因為用了DbUtils元件,屬性要與資料庫中欄位一致)
* @author Jie.Yuan
*
*/
public class Employee {
private int empId;			// 員工id
private String empName;		// 員工名稱
private int dept_id;		// 部門id
//......省略getter和setter方法

5.接下來是DAO資料訪問層的設計,先設計介面,然後再設計實現類(IEmployeeDao.java &EmployeeDaoImpl.java)

package cn.itcast.dao;
import cn.itcast.entity.Employee;
import cn.itcast.utils.PageBean;
/**
* 2. 資料訪問層,介面設計
* @author Jie.Yuan
*
*/
public interface IEmployeeDao {
/**
* 分頁查詢資料
*/
public void getAll(PageBean<Employee> pb);
/**
* 查詢總記錄數
*/
public int getTotalCount();
}

實現類

package cn.itcast.dao.impl;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import cn.itcast.dao.IEmployeeDao;
import cn.itcast.entity.Employee;
import cn.itcast.utils.JdbcUtils;
import cn.itcast.utils.PageBean;
/**
* 2. 資料訪問層實現
* @author Jie.Yuan
*
*/
public class EmployeeDao implements IEmployeeDao {
@Override
public void getAll(PageBean<Employee> pb) {
//2. 查詢總記錄數;  設定到pb物件中
int totalCount = this.getTotalCount();
pb.setTotalCount(totalCount);
/*
* 問題: jsp頁面,如果當前頁為首頁,再點選上一頁報錯!
*              如果當前頁為末頁,再點下一頁顯示有問題!
* 解決:
* 	   1. 如果當前頁 <= 0;       當前頁設定當前頁為1;
* 	   2. 如果當前頁 > 最大頁數;  當前頁設定為最大頁數
*/
// 判斷
if (pb.getCurrentPage() <=0) {
pb.setCurrentPage(1);					    // 把當前頁設定為1
} else if (pb.getCurrentPage() > pb.getTotalPage()){
pb.setCurrentPage(pb.getTotalPage());		// 把當前頁設定為最大頁數
}
//1. 獲取當前頁: 計算查詢的起始行、返回的行數
int currentPage = pb.getCurrentPage();
int index = (currentPage -1 ) * pb.getPageCount();		// 查詢的起始行
int count = pb.getPageCount();							// 查詢返回的行數
//3. 分頁查詢資料;  把查詢到的資料設定到pb物件中
String sql = "select * from employee limit ?,?";
try {
// 得到Queryrunner物件
QueryRunner qr = JdbcUtils.getQueryRuner();
// 根據當前頁,查詢當前頁資料(一頁資料)
List<Employee> pageData = qr.query(sql, new BeanListHandler<Employee>(Employee.class), index, count);
// 設定到pb物件中
pb.setPageData(pageData);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int getTotalCount() {
String sql = "select count(*) from employee";
try {
// 建立QueryRunner物件
QueryRunner qr = JdbcUtils.getQueryRuner();
// 執行查詢, 返回結果的第一行的第一列
Long count = qr.query(sql, new ScalarHandler<Long>());
return count.intValue();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

7.service層設計,同樣是先介面,後實現類

IEmployeeService.java & EmployeeService.java
package cn.itcast.service;
import cn.itcast.entity.Employee;
import cn.itcast.utils.PageBean;
/**
* 3. 業務邏輯層介面設計
* @author Jie.Yuan
*
*/
public interface IEmployeeService {
/**
* 分頁查詢資料
*/
public void getAll(PageBean<Employee> pb);
}

實現類

package cn.itcast.service.impl;
import cn.itcast.dao.IEmployeeDao;
import cn.itcast.dao.impl.EmployeeDao;
import cn.itcast.entity.Employee;
import cn.itcast.service.IEmployeeService;
import cn.itcast.utils.PageBean;
/**
* 3. 業務邏輯層,實現
* @author Jie.Yuan
*
*/
public class EmployeeService implements IEmployeeService {
// 建立Dao例項
private IEmployeeDao employeeDao = new EmployeeDao();
@Override
public void getAll(PageBean<Employee> pb) {
try {
employeeDao.getAll(pb);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

8.最終,表現層Servlet的設計IndexServlet.java

package cn.itcast.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.itcast.entity.Employee;
import cn.itcast.service.IEmployeeService;
import cn.itcast.service.impl.EmployeeService;
import cn.itcast.utils.PageBean;
/**
* 4. 控制層開發
* @author Jie.Yuan
*
*/
public class IndexServlet extends HttpServlet {
// 建立Service例項
private IEmployeeService employeeService = new EmployeeService();
// 跳轉資源
private String uri;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try {
//1. 獲取“當前頁”引數;  (第一次訪問當前頁為null) 
String currPage = request.getParameter("currentPage");
// 判斷
if (currPage == null || "".equals(currPage.trim())){
currPage = "1";  	// 第一次訪問,設定當前頁為1;
}
// 轉換
int currentPage = Integer.parseInt(currPage);
//2. 建立PageBean物件,設定當前頁引數; 傳入service方法引數
PageBean<Employee> pageBean = new PageBean<Employee>();
pageBean.setCurrentPage(currentPage);
//3. 呼叫service  
employeeService.getAll(pageBean);    // 【pageBean已經被dao填充了資料】
//4. 儲存pageBean物件,到request域中
request.setAttribute("pageBean", pageBean);
//5. 跳轉 
uri = "/WEB-INF/list.jsp";
} catch (Exception e) {
e.printStackTrace();  // 測試使用
// 出現錯誤,跳轉到錯誤頁面;給使用者友好提示
uri = "/error/error.jsp";
}
request.getRequestDispatcher(uri).forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}

9.這是真的最後一步了,設計jsp頁面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!-- 引入jstl核心標籤庫 -->
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>分頁資料查詢</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">    
</head>
<body>
<table border="1" width="80%" align="center" cellpadding="5" cellspacing="0">
<tr>
<td>序號</td>
<td>員工編號</td>
<td>員工姓名</td>
</tr>
<!-- 迭代資料 -->
<c:choose>
<c:when test="${not empty requestScope.pageBean.pageData}">
<c:forEach var="emp" items="${requestScope.pageBean.pageData}" varStatus="vs">
<tr>
<td>${vs.count }</td>
<td>${emp.empId }</td>
<td>${emp.empName }</td>
</tr>
</c:forEach>
</c:when>
<c:otherwise>
<tr>
<td colspan="3">對不起,沒有您查詢的資料</td>
</tr>
</c:otherwise>
</c:choose>
<tr>
<td colspan="3" align="center">
當前${requestScope.pageBean.currentPage }/${requestScope.pageBean.totalPage }頁     
<a href="${pageContext.request.contextPath }/index?currentPage=1">首頁〉</a>
<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage-1}">上一頁 </a>
<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.currentPage 1}">下一頁 </a>
<a href="${pageContext.request.contextPath }/index?currentPage=${requestScope.pageBean.totalPage}">末頁</a>
</td>
</tr>
</table>
</body>
</html>

下面的內容不知道怎麼回事無法刪除,無視就好