分页简介

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

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

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

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

分页的分类

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

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. LAMP 搭建

    p { margin-bottom: 0.25cm; line-height: 120% } LAMP 搭建 承 Ubuntu 17.10.1安装, 定制. 参考 电子工业出版社, Ubuntu完美应 ...

  2. Mego开发文档 - 保存关系数据

    保存关系数据 由于没有对象的更改跟踪,因此关系的操作需要开发者明确指定,在成功执行后Mego会影响到相应的关系属性中. 添加关系 在以下示例中如果成功执行则source的Customer属性会变为ta ...

  3. 深入了解GOT,PLT和动态链接

    之前几篇介绍exploit的文章, 有提到return-to-plt的技术. 当时只简单介绍了 GOT和PLT表的基本作用和他们之间的关系, 所以今天就来详细分析下其具体的工作过程. 本文所用的依然是 ...

  4. OAuth2.0学习(1-13)oauth2.0 的概念:资源、权限(角色)和scope

    mkk 关于资源的解释 : https://andaily.com/blog/?cat=19 resource用于将系统提供的各类资源进行分组管理, 每一个resource对应一个resource-i ...

  5. CentOS7配置php7.0支持redis

    配置之前应该是环境已经搭好了,phpinfo的页面可以加载出来. 使用git clone下载git上的phpredis扩展包 [root@VM_103_117_centos ]#git clone   ...

  6. asp.net core 六 Oracle ORM

         .netcore 中 Oracle ORM      在真正将项目移植到.netcore下,才发现会有很多问题,例如访问Oracle,问题出现的时间在2017年底          参考连接 ...

  7. JavaScript高级程序设计(第3版)

    准备开始分享阅读笔记 自己收获的同时 让更多的人收益 这也是我力荐的学习javascript基础的书籍

  8. JavaScript的作用;JS常见的三种对话框;==和===的区别;函数内部参数数组arguments在函数内部打印实参;JS的误区:没有块级作用域

    JS:客户端(浏览器)脚本语言 弱类型 基于原型 事件驱动 不需要编译(直接运行)   JS的作用:表单验证,减轻服务端的压力 添加页面动画效果  动态更改页面内容  Ajax网络请求 (一)常见的对 ...

  9. [Codeforces 176B]Word Cut

    Description 题库链接 给你两个字符串 \(S\) 和 \(T\) ,准许你 \(k\) 次操作,每次将字符串左右分成两个非空的部分,再交换位置,问你有多少种不同的操作方法将 \(S\) 串 ...

  10. [SDOI 2008]仪仗队

    Description 作为体育委员,C君负责这次运动会仪仗队的训练.仪仗队是由学生组成的N * N的方阵,为了保证队伍在行进中整齐划一,C君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是 ...