分页简介

分页功能在网页中是非常常见的一个功能,其作用也就是将数据分割成多个页面来进行显示。

  • 使用场景: 当取到的数据量达到一定的时候,就需要使用分页来进行数据分割。

当我们不使用分页功能的时候,会面临许多的问题:

  • 客户端的问题: 如果数据量太多,都显示在同一个页面的话,会因为页面太长严重影响到用户的体验,也不便于操作,也会出现加载太慢的问题。
  • 服务端的问题: 如果数据量太多,可能会造成内存溢出,而且一次请求携带的数据太多,对服务器的性能也是一个考验。

分页的分类

分页的实现分为真分页和假分页两种,也就是物理分页和逻辑分页。

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学习资料

Java Web -【分页功能】详解的更多相关文章

  1. java web之Filter详解

    java web之Filter详解 2012-10-20 0 个评论 作者:chenshufei2 收藏 我要投稿 .概念: Filter也称之为过滤器,它是Servlet技术中比较激动人心的技术,W ...

  2. java web.xml配置详解(转)

    源出处:java web.xml配置详解 1.常规配置:每一个站的WEB-INF下都有一个web.xml的设定文件,它提供了我们站台的配置设定. web.xml定义: .站台的名称和说明 .针对环境参 ...

  3. 《Tomcat与Java Web开发技术详解》思维导图

    越想构建上层建筑,就越觉得底层基础很重要.补课系列. 书是良心书,就是太基础了,正适合补课. [纯文字版] Tomcat与Java Web开发技术详解 Servlet Servlet的生命周期 初始化 ...

  4. Java web.xml 配置详解

    在项目中总会遇到一些关于加载的优先级问题,近期也同样遇到过类似的,所以自己查找资料总结了下,下面有些是转载其他人的,毕竟人家写的不错,自己也就不重复造轮子了,只是略加点了自己的修饰. 首先可以肯定的是 ...

  5. Java Web(一) Servlet详解!!

    这篇文章到上一篇,距离的有点遥远呀,隔了大概有两个月把,中间在家过了个年,哈哈~ 现在重新开始拾起,最近在看一本个人觉得很棒的书,<Java Web 整合开发王者归来>,现在写的这一系列基 ...

  6. (转)Java Web(一) Servlet详解!!

    https://www.cnblogs.com/whgk/p/6399262.html 这篇文章到上一篇,距离的有点遥远呀,隔了大概有两个月把,中间在家过了个年,哈哈~ 现在重新开始拾起,最近在看一本 ...

  7. java web.xml配置详解

    1.启动一个WEB项目的时候,WEB容器会去读取它的配置文件web.xml,读取<listener>和<context-param>两个结点. 2.紧急着,容创建一个Servl ...

  8. Django2.1中的分页功能详解

    django的分页功能类将我们常用的多种方法均封装在Paginator类,根据这些方法我们均可深度定制我们的分页功能. 首先来看看[Paginator] 类的构造方法: class Paginator ...

  9. Java Web开发之详解JSP

    JSP作为Java Web开发中比较重要的技术,一般当作视图(View)的技术所使用,即用来展现页面.Servlet由于其本身不适合作为表现层技术,所以一般被当作控制器(Controller)所使用, ...

  10. Java Web(二) Servlet详解

    什么是Servlet? Servlet是运行在Web服务器中的Java程序.Servlet通常通过HTTP(超文本传输协议)接收和响应来自Web客户端的请求.Java Web应用程序中所有的请求-响应 ...

随机推荐

  1. jenkins简单安装及配置(Windows环境)

    jenkins是一款跨平台的持续集成和持续交付.基于Java开发的开源软件,提供任务构建,持续集成监控的功能,可以使开发测试人员更方便的构建软件项目,提高工作效率. Windows平台下,一般安装方法 ...

  2. ArrayList、Vector、LinkedList、HashMap、HashTable的存储性能和特性

    ArrayList和Vector都是使用数组方式存储数据,次数组元素大于实际存储的数据以便添加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数 ...

  3. 新概念英语(1-99)Ow!

    Lesson 99 Owl! 啊哟! Listen to the tape then answer this question. Must Andy go to see the doctor?听录音, ...

  4. Android fragment切换后onresume时报 Attempt to write to field 'int android.support.v4.app.Fragment.mNextAnim'

    动态加载fragment以后,调用了remove方法移除Fragment,在返回来的时候报 Attempt to write to field 'int android.support.v4.app. ...

  5. Python基础题

    1. 执行Python脚本的两种方式: Chmod +x 脚本 ./脚本(路径的方式) Python 脚本 2. 简述位.字节的关系 一个字节=8位 3. 简述ASCII.unicode.utf-8/ ...

  6. geotrellis使用(三十七)COG 基础介绍

    前言 Geotrellis 已经迭代到了 2.0 版本(截止写作此文时为 2.0.0-SNAPSHOT 版),2.0 版多了很多新的特性,其中最重要的应该就是 COG,COG 是什么鬼?刚看到时我也是 ...

  7. 我花了 8 小时,"掌握"了一下 Flutter | Flutter 中文站上线

    Hi,大家好,我是承香墨影! 距离 Google 在 2018 世界移动大会上发布 Flutter 的 Beta 版本,Flutter 是 Google 用以帮助开发者在 Android 和 iOS ...

  8. 去除Eclipse中js报错的问题

    第一步:    去除eclipse的JS验证:        将windows->preference->Java Script->Validator->Errors/Warn ...

  9. CSS禁止输入之readonly VS disable

    Readonly只针对input(text / password)和textarea有效,而disabled对于所有的表单元素都有效,包括select, radio, checkbox, button ...

  10. [LeetCode] Longest Univalue Path 最长相同值路径

    Given a binary tree, find the length of the longest path where each node in the path has the same va ...