Java Web -【分頁功能】詳解


分頁簡介

分頁功能在網頁中是非常常見的一個功能,其作用也就是將數據分割成多個頁面來進行顯示。

  • 使用場景: 當取到的數據量達到一定的時候,就需要使用分頁來進行數據分割。

當我們不使用分頁功能的時候,會面臨許多的問題:

  • 客戶端的問題: 如果數據量太多,都顯示在同一個頁面的話,會因為頁面太長嚴重影響到用戶的體驗,也不便於操作,也會出現加載太慢的問題。
  • 服務端的問題: 如果數據量太多,可能會造成內存溢出,而且一次請求攜帶的數據太多,對服務器的性能也是一個考驗。

分頁的分類

分頁的實現分為真分頁和假分頁兩種,也就是物理分頁和邏輯分頁。

1.真分頁(物理分頁):

  • 實現原理: SELECT * FROM xxx [WHERE...] LIMIT #{param1}, #{param2}
    第一個參數是開始數據的索引位置
    第二個參數是要查詢多少條數據
  • 優點: 不會造成內存溢出
  • 缺點: 翻頁的速度比較慢

2.假分頁(邏輯分頁):

  • 實現原理: 一次性將所有的數據查詢出來放在內存之中,每次需要查詢的時候就直接從內存之中去取出相應索引區間的數據
  • 優點: 分頁的速度比較快
  • 缺點: 可能造成內存溢出

傳統的分頁方式

對於假分頁的實現方式很簡單,只需要准備一個集合保存從數據庫中取出的所有數據,然后根據當前頁面的碼數,取出對應范圍的數據顯示就好了,我們這里基於物理分頁來實現。

分頁的原理

  • 頁面中的數據有:
    結果集:通過 SQL 語句查詢得來的——List
  • 分頁條中的數據有:
    當前頁:用戶傳遞到后台——currentPage
    總頁數:計算的來——totalPage
    上一頁:計算的來——prePage
    下一頁:計算的來——nextPage
    尾頁:計算的來(總頁數)——lastPage
    頁面大小(即每一頁顯示的條數):用戶傳遞到后台——count
    總條數:通過 SQL 語句查詢得來的——totalCount

可以發現頁面功能中需要用到的數據有兩個是需要通過 SQL 語句查詢得來的:一個是頁面中顯示的數據 List ,另一個是數據的總條數 totalCount,分別對應以下兩條 SQL 語句:

  • 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 - 1varStatus 相當於是循環變量

  • 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 語句:

<!--&lt;!&ndash; 查詢數據條目 &ndash;&gt;-->
<!--<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學習資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM