分頁簡介
分頁功能在網頁中是非常常見的一個功能,其作用也就是將數據分割成多個頁面來進行顯示。
- 使用場景: 當取到的數據量達到一定的時候,就需要使用分頁來進行數據分割。
當我們不使用分頁功能的時候,會面臨許多的問題:
- 客戶端的問題: 如果數據量太多,都顯示在同一個頁面的話,會因為頁面太長嚴重影響到用戶的體驗,也不便於操作,也會出現加載太慢的問題。
- 服務端的問題: 如果數據量太多,可能會造成內存溢出,而且一次請求攜帶的數據太多,對服務器的性能也是一個考驗。
分頁的分類
分頁的實現分為真分頁和假分頁兩種,也就是物理分頁和邏輯分頁。
1.真分頁(物理分頁):
- 實現原理:
SELECT * FROM xxx [WHERE...] LIMIT #{param1}, #{param2}
第一個參數是開始數據的索引位置
第二個參數是要查詢多少條數據- 優點: 不會造成內存溢出
- 缺點: 翻頁的速度比較慢
2.假分頁(邏輯分頁):
- 實現原理: 一次性將所有的數據查詢出來放在內存之中,每次需要查詢的時候就直接從內存之中去取出相應索引區間的數據
- 優點: 分頁的速度比較快
- 缺點: 可能造成內存溢出
傳統的分頁方式
對於假分頁的實現方式很簡單,只需要准備一個集合保存從數據庫中取出的所有數據,然后根據當前頁面的碼數,取出對應范圍的數據顯示就好了,我們這里基於物理分頁來實現。
分頁的原理
- 頁面中的數據有:
結果集:通過 SQL 語句查詢得來的——List- 分頁條中的數據有:
當前頁:用戶傳遞到后台——currentPage
總頁數:計算的來——totalPage
上一頁:計算的來——prePage
下一頁:計算的來——nextPage
尾頁:計算的來(總頁數)——lastPage
頁面大小(即每一頁顯示的條數):用戶傳遞到后台——count
總條數:通過 SQL 語句查詢得來的——totalCount
可以發現頁面功能中需要用到的數據有兩個是需要通過 SQL 語句查詢得來的:一個是頁面中顯示的數據 List
SELECT * FROM student LIMIT #{param1}, #{param2}
SELECT COUNT(*) FROM student
通過計算得到的數據有:
- 總頁數:totalPage
總頁數 = 總條數 % 頁面大小 == 0 ? 總條數 / 頁面大小 : 總條數 / 頁面大小 + 1- 上一頁:prePage
上一頁 = 當前頁 - 1 > = 1 ? 當前頁 - 1 : 1- 下一頁:nextPage
下一頁 = 當前頁 + 1 <= totalPage ? 當前頁 + 1 : totalPage- 尾頁:lastPage
尾頁 = 總條數 % 頁面大小 == 0 ? 總條數 - 頁面大小 : 總條數 - 總條數 % 頁面大小
用戶傳遞的數據:
- 當前頁:currentPage
- 頁面大小:count
所有我們可以創建一個 Page 工具類備用:
public class Page {
int start; // 開始數據的索引
int count; // 每一頁的數量
int total; // 總共的數據量
/**
* 提供一個構造方法
* @param start
* @param count
*/
public Page(int start, int count) {
super();
this.start = start;
this.count = count;
}
/**
* 判斷是否有上一頁
* @return
*/
public boolean isHasPreviouse(){
if(start==0)
return false;
return true;
}
/**
* 判斷是否有下一頁
* @return
*/
public boolean isHasNext(){
if(start==getLast())
return false;
return true;
}
/**
* 計算得到總頁數
* @return
*/
public int getTotalPage(){
int totalPage;
// 假設總數是50,是能夠被5整除的,那么就有10頁
if (0 == total % count)
totalPage = total /count;
// 假設總數是51,不能夠被5整除的,那么就有11頁
else
totalPage = total / count + 1;
if(0==totalPage)
totalPage = 1;
return totalPage;
}
/**
* 計算得到尾頁
* @return
*/
public int getLast(){
int last;
// 假設總數是50,是能夠被5整除的,那么最后一頁的開始就是45
if (0 == total % count)
last = total - count;
// 假設總數是51,不能夠被5整除的,那么最后一頁的開始就是50
else
last = total - total % count;
last = last<0?0:last;
return last;
}
/* getter and setter */
}
前台實現分頁設計
首先我們在前台需要完成我們分頁條的設計,這里可以直接引入 Bootstrap 來完成:
上面是使用 Bootstrap 實現一個分頁條的簡單例子,如果不熟悉的童鞋可以去菜鳥教程中查看:點這里
簡單版本的分頁條
為了便於理解,我們先來實現一個簡單版本的分頁條吧:
- 首頁超鏈:指向了 start 為 0 的首頁
<li>
<a href="?page.start=0">
<span>«</span>
</a>
</li>
- 上一頁超鏈:
<li >
<a href="?page.start=${page.start-page.count}">
<span>‹</span>
</a>
</li>
- 下一頁超鏈:
<li >
<a href="?page.start=${page.start+page.count}">
<span>›</span>
</a>
</li>
- 最后一頁超鏈:指向了最后一頁
<li >
<a href="?page.start=${page.last}">
<span>»</span>
</a>
</li>
- 中間頁:
<c:forEach begin="0" end="${page.totalPage-1}" varStatus="status">
<li>
<a href="?page.start=${status.index*page.count}" class="current">${status.count}</a>
</li>
</c:forEach>
- 所以寫完看起來會是這樣子的:
<nav>
<ul class="pagination">
<li>
<a href="?page.start=0">
<span>«</span>
</a>
</li>
<li >
<a href="?page.start=${page.start-page.count}">
<span>‹</span>
</a>
</li>
<c:forEach begin="0" end="${page.totalPage-1}" varStatus="status">
<li>
<a href="?page.start=${status.index*page.count}" class="current">${status.count}</a>
</li>
</c:forEach>
<li >
<a href="?page.start=${page.start+page.count}">
<span>›</span>
</a>
</li>
<li >
<a href="?page.start=${page.last}">
<span>»</span>
</a>
</li>
</ul>
</nav>
- 存在的問題:
① 沒有邊界判斷,即在首頁仍然可以點擊前一頁,不符合邏輯也影響用戶體驗
② 會顯示完所有的分頁,即如果 totalPage 有50頁,那么分頁欄將會顯得特別長,影響體驗
改良版本的分頁條
1.寫好頭和尾
<nav class="pageDIV">
<ul class="pagination">
.....
</ul>
</nav>
2.寫好«
‹
這兩個功能按鈕
使用 <c:if>
標簽來增加邊界判斷,如果沒有前面的頁碼了則設置為disable狀態
<li <c:if test="${!page.hasPreviouse}">class="disabled"</c:if>>
<a href="?page.start=0">
<span>«</span>
</a>
</li>
<li <c:if test="${!page.hasPreviouse}">class="disabled"</c:if>>
<a href="?page.start=${page.start-page.count}">
<span>‹</span>
</a>
</li>
再通過 JavaScrip 代碼來完成禁用功能:
<script>
$(function () {
$("ul.pagination li.disabled a").click(function () {
return false;
});
});
</script>
3.完成中間頁碼的編寫
<c:forEach begin="0" end="${page.totalPage-1}" varStatus="status">
<c:if test="${status.count*page.count-page.start<=30 && status.count*page.count-page.start>=-10}">
<li <c:if test="${status.index*page.count==page.start}">class="disabled"</c:if>>
<a
href="?page.start=${status.index*page.count}"
<c:if test="${status.index*page.count==page.start}">class="current"</c:if>
>${status.count}</a>
</li>
</c:if>
</c:forEach>
從 0
循環到 page.totalPage - 1
,varStatus
相當於是循環變量
- status.count 是從1開始遍歷
- status.index 是從0開始遍歷
- 要求:顯示當前頁碼的前兩個和后兩個就可,例如當前頁碼為3的時候,就顯示 1 2 3(當前頁) 4 5 的頁碼
- 理解測試條件:
-10 <= 當前頁*每一頁顯示的數目 - 當前頁開始的數據編號 <= 30
- 只要理解了這個判斷條件,其他的就都好理解了
- 注意: 測試條件是需要根據項目的需求動態改變的,不是萬能的!
后台中的分頁
首頁在項目中引入上面提到的 Page 工具類,然后我們在 DAO 類中使用 LIMIT 關鍵字來查詢數據庫中的信息:
public List<Student> list() {
return list(0, Short.MAX_VALUE);
}
public List<Student> list(int start, int count) {
List<Student> students = new ArrayList<>();
String sql = "SELECT * FROM student ORDER BY student_id desc limit ?,?";
try (Connection c = DBUtil.getConnection(); PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, start);
ps.setInt(2, count);
// 獲取結果集...
} catch (SQLException e) {
e.printStackTrace();
}
return students;
}
在 Servlet 中獲取分頁參數並使首頁顯示的 StudentList 用 page 的參數來獲取:
// 獲取分頁參數
int start = 0;
int count = 10;
try {
start = Integer.parseInt(req.getParameter("page.start"));
count = Integer.parseInt(req.getParameter("page.count"));
} catch (Exception e) {
}
Page page = new Page(start, count);
List<Student> students = studentDAO.list(page.getStart(), page.getCount());
....
// 共享數據
req.setAttribute("page", page);
req.setAttribute("students", students);
以上即可完成分頁功能,但這是基於 Servlet 的版本,在之前寫過的項目(學生管理系統(簡易版))中實際的使用了這種方法,感興趣的可以去看一下。
SSM 中的分頁
在 SSM 項目中,我們可以使用 MyBatis 的一款分頁插件: PageHelper 來幫助我們更加簡單的完成分頁的需求,官網在這里: PageHelper
在這里,我們演示一下如何使用上面的工具重構我們之前寫過的 SSM 項目 —— 學生管理系統-SSM 版
第一步:添加相關 jar 依賴包
PageHelper 需要依賴兩個 jar 包,我們直接在 pom.xml 中增加兩個 jar 包依賴:
<!-- pageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2-beta</version>
</dependency>
<!--jsqlparser-->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.0</version>
</dependency>
第二步:配置相關環境
在 MyBatis 的 SessionFactory 配置中新增加一個屬性名 plugins 的配置:
<!-- 配置SqlSessionFactory對象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入數據庫連接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 掃描entity包 使用別名 -->
<property name="typeAliasesPackage" value="cn.wmyskxz.entity"/>
<!-- 掃描sql配置文件:mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
<!-- 讓MyBatis支持PageHelper插件 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置參數,一行配置一個 -->
<value>
</value>
</property>
</bean>
</array>
</property>
</bean>
第三步:重構項目
首先我們把 LIMIT 關鍵字從映射文件中干掉:
<!-- 查詢從start位置開始的count條數據-->
<select id="list" resultMap="student">
SELECT * FROM student ORDER BY student_id desc
</select>
然后注釋掉查詢數據總條數的 SQL 語句:
<!--<!– 查詢數據條目 –>-->
<!--<select id="getTotal" resultType="int">-->
<!--SELECT COUNT(*) FROM student-->
<!--</select>-->
在 Dao 類和 Service 類中修改相應的地方:
然后修改掉 StudentController 中的方法:
@RequestMapping("/listStudent")
public String listStudent(HttpServletRequest request, HttpServletResponse response) {
// 獲取分頁參數
int start = 0;
int count = 10;
try {
start = Integer.parseInt(request.getParameter("page.start"));
count = Integer.parseInt(request.getParameter("page.count"));
} catch (Exception e) {
}
Page page = new Page(start, count);
// 使用 PageHelper 來設置分頁
PageHelper.offsetPage(page.getStart(),page.getCount());
List<Student> students = studentService.list();
// 使用 PageHelper 來獲取總數
int total = (int) new PageInfo<>(students).getTotal();
page.setTotal(total);
request.setAttribute("students", students);
request.setAttribute("page", page);
return "listStudent";
}
重啟服務器,能看到也能夠正確的使用分頁功能。
總結
其實我自己對於這個工具比較無感..因為只是弱化了少一部分的功能,並沒有我想象中的那樣 “智能” ,也沒有看到什么好的博文能夠點通我的認知,希望了解的大大們能無私分享一下,謝謝!
歡迎轉載,轉載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學習之路以及各種Java學習資料