本篇讲诉如何在页面中通过操作数据库来完成数据显示的分页功能。当一个操作数据库进行查询的语句返回的结果集内容如果过多,那么内存极有可能溢出,所以在大数据的情况下分页是必须的。当然分页能通过很多种方式来实现,而这里我们采用的是操作数据库的方式,而且在这种方式中,最重要的是带限制条件的查询SQL语句:

select name from user limit m,n

  其中m与n为数字。n代表需要获取多少行的数据项,而m代表从哪开始(以0为起始),例如我们想从user表中先获取前五行数据项(1-5)的name列数据,则SQL为:

select name from user limit 0,5;

  那么如果要继续往下看一页五行的数据项(6-10)则下一步的SQL应该为:

select name from user limit 5,5;

  再下一页五行的数据项(11-15)的SQL就为:

select name from user limit 15,5;

。。。

  如果对上面的SQL语句不熟悉的话,请先查询相关文档再来看本篇内容。

  我们先来看看“百度贴吧”中的分页效果:

  

  

  

  我选取了首页、次页和尾页三种情况的显示效果,可以看到这个分页显示的效果是比较灵活多变的,开发者可以依据自己的爱好进行展示,但是实质是不变的。

  那么接下来我们将做出如下效果的页面显示:

  

  在这个页面中,其实按面向对象的思考方式,这个页面就是一个对象,稍后我们会说到,先来看看在该页面下方的页码分页显示,探究当用户点击之后,分页请求是如何一步步到底层的。

基本流程就如上图所示,那么我们来分析这个过程:

  第一步:当用户在页面上点击某一页,或者下一页、上一页等等这些超链接,根据MVC设计模式,这些请求都是交给Servlet处理。

  第二步:在Servlet中,首先应该将请求对象带来的信息封装到对象中,由于我们是要查询数据库,因此必须封装成一个查询的条件对象,这里举例为“QueryInfo”自定义对象,在刚对象中包含当前页、每页多少条数据、等会查询数据库从哪开始等信息,只有拥有了这些信息,才能在数据库查询的时候能根据顺序往下翻页。

  第三步、第四步:根据web工程的三层设计模式,业务从service层一步步到dao层。

  第五步、第六步:通过上层传下来的QueryInfo对象,根据里面封装的信息开始对数据库进行操作,使用select name from user limit startIndex,pageSize 这样的SQL命令,将查询到的结果集返回给dao层。

  第七步:dao层根据从数据库返回的结果集,提取出用户想看的页面数据,这里我们将页面数据都封装到一个集合中,除此之外为了之后在页面上能显示页码之类的数据,还必须要获取到查询的总记录数。前面这两个信息数据我们以“QueryResult”自定义对象来封装。

  第八步、第九步:在service层,通过dao传递上来的查询结果“QueryResult”对象,和一开始的查询信息“QueryInfo”对象,来构建页面显示信息,例如页面数据、总记录数、总页数、当前页、上一页、下一页、页码条等等,我们将其都封装进“PageBean”这个JavaBean中,对于JSP中要显示的动态数据,我们只需要提取PageBean对象中的属性即可。其实PageBean的对象需要哪些属性,只要看在JSP页面中我们想显示什么数据就行了,设计还是很简单。

  第十步、第十一步:service层将页面所需要的信息封装进PageBean对象后,将其传给web层的Servlet,由MVC模式,Servlet再将PageBean对象封装进请求交给JSP来显示。

  了解完一个分页功能的实现流程之后,下面我将开始进行分页的实现。

  上面的步骤涉及到三个实体对象,分别是QueryInfo,QueryResult、PageBean,而我们在工程中先构建这三个实体,在这三个实体中,有些属性是可以根据别的属性计算出来的,我们没必要提供setter方法。

实体QueryInfo对象:

 public class QueryInfo {
private int currentPage = 1; //用户当前看的页数
private int pageSize = 10; //每页多少条显示数据
private int startIndex; //记住用户想看的页的数据在数据库的起始位置 。。。 //此处省略currentPage和pageSize两个属性的set和get方法 public int getStartIndex() {
this.startIndex = (this.currentPage-1)*this.pageSize;
return startIndex;
}
}

  注:在查询信息对象QueryInfo中,currentPage和pageSize属性都设置了默认值,如果用户没有特意设置每页显示多少条数据,则根据默认值进行计算。另外由于startIndex属性可以由另外两个属性计算出,因此无需set方法。

实体PageBean对象:

 public class PageBean {
private List contentData; //保存页面数据
private int totalRecords; //查询到的总记录数
private int currentPage; //用户当前看的页数
private int pageSize; //每页多少条显示数据
private int totalPages; //总页数
private int previousPage; //上一页
private int nextPage; //下一页
private int[] pageBar; //页码条 //1,contentData可以从QueryResult对象中获取
。。。//此处省略contentData属性的set和get方法 //2,totalRecords可以从QueryResult对象中获取
。。。//此处省略contentData属性的set和get方法 //3,currentPage可以从QueryInfo对象中获取
。。。//此处省略contentData属性的set和get方法 //4,pageSize可以从QueryInfo对象中获取
。。。//此处省略contentData属性的set和get方法 //5,总页数可以由总页数和页面数据大小这两个属性计算,因此无需set方法
public int getTotalPages() {
if(totalRecords % pageSize ==0){
totalPages = totalRecords / pageSize;
}else{
totalPages = totalRecords / pageSize + 1;
}
return totalPages;
} //6,上一页可以根据当前页计算,因此无需set方法
public int getPreviousPage() {
if(currentPage == 1) {
previousPage = 1;
}else {
previousPage = currentPage - 1;
}
return previousPage;
} //7,下一页可以根据当前页计算,因此无需set方法
public int getNextPage() {
if(currentPage == totalPages) {
nextPage = totalPages;
}else {
nextPage = currentPage + 1;
}
return nextPage;
} //8,页码条可以由总页数来计算显示,因此无需set方法
public int[] getPageBar() {
pageBar = null;
int startIndex ;
int endIndex ;
if(totalPages<10) {
pageBar = new int[totalPages];
startIndex = 1;
endIndex = totalPages; }else{
pageBar = new int[10];
startIndex = currentPage-5;
endIndex = currentPage+4;
if(startIndex<1) {
startIndex = 1;
endIndex = 10;
}
if(endIndex>totalPages) {
startIndex = totalPages-10+1;
endIndex = totalPages;
}
}
int index = 0;
for(int i=startIndex;i<=endIndex;i++) {
pageBar[index] = i;
index++;
}
return pageBar;
}}

注:PageBean对象属性会比较多,因为这些属性都是要在页面上显示的内容。虽然属性多,但是由很多属性值可以通过别的属性计算得到,另外的属性可以通过别的对象属性得到。

  尤其是页码条pageBar这个属性,这里我的设计是,如果总页数不超过10页的话,那么页码条显示的个数就为总页数;如果总页数超过10页,那么页码条固定显示10个页码,同时如果当前页在最前6个页则页码条保持不变,在中间部分的当前页会保持在页码条的中间位置(前面5个页码,后面4个页码,当前页在第6个位置)。如果当前页到最后部分也是同理。

  上面三个对象设计完成后,我们就要来考虑在分页流程中不同层对查询信息的处理方式。

  按从下到上的开发流程,首先是dao层,该层必须通过请求发来的查询信息来对数据库进行操作,也就是本文最开始讲解的SQL语句的两个参数是执行数据库操作的关键,本文以显示User用户为分页案例,在数据库中为user表。因此在处理User对象的dao层实现类UserDaoImpl中,查询方法为pageQuery,返回上面刚刚定义的QueryResult对象。

  注:该工程是博客《JDBC操作数据库的学习(2)》和《在JDBC中使用PreparedStatement代替Statement,同时预防SQL注入》中工程的扩展,下面使用到JDBC的工具类JdbcUtils即是在《JDBC操作数据库的学习(2)》中的定义。

  下面的代码对应流程图中的第五、六、七步骤:

 package com.fjdingsd.dao.impl;
public class UserDaoImpl implements UserDao {
public QueryResult pageQuery(int startIndex,int pageSize) {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
QueryResult result = new QueryResult();
try{
conn = JdbcUtils.getConnection();
String sql = "select * from user limit ?,?";
st = conn.prepareStatement(sql);
st.setInt(1, startIndex);
st.setInt(2, pageSize);
rs = st.executeQuery();
List contentList = new ArrayList();
while(rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
contentList.add(user);
}
result.setContentData(contentList);
//获取了页面数据后还没结束,还得获取总记录数
sql = "select count(*) from user";
st = conn.prepareStatement(sql);
rs = st.executeQuery();
if(rs.next()) {
int totalRecords = rs.getInt(1); //rs.getInt("count(*)")也是可以的
result.setTotalRecords(totalRecords);
}
return result;
}catch (Exception e) {
throw new RuntimeException(e);
}finally{
JdbcUtils.release(conn, st, rs);
}
}
}

  上面在dao层对User对象处理的实现类UserDaoImpl已经处理好了分页查询,该pageQuery方法返回的QueryResult对象正是在service层中处理User对象的业务的方法所需要的参数。在service层中,我们需要根据查询得到的结果QueryResult对象,来获取页面显示所需要的对象PageBean。

  下面的代码对应流程图的第三、四和第八、九步骤:

 package com.fjdingsd.service;
public class UserServiceImpl {
private UserDao userDao = new UserDaoImpl(); //通常使用工程模式获取实现类对象,这里为了简便直接采用实现类的构造器 public PageBean pageQuery(QueryInfo info) {
//获取对应dao的实现类中的查询到的结果数据
QueryResult result = userDao.pageQuery(info.getStartIndex(), info.getPageSize()); //根据dao的查询结果,生成页面显示需要的PageBean
PageBean page = new PageBean();
page.setContentData(result.getContentData());
page.setTotalRecords(result.getTotalRecords());
page.setCurrentPage(info.getCurrentPage());
page.setPageSize(info.getPageSize()); return page;
}
}

  上面在service层将查询到的结果对象封装成页面显示所需要的对象PageBean,service层需要将这个对象交给web层的Servlet来处理,其实这个Servlet也是最开始处理请求对象的Servlet,因为最开始要想生成查询信息QueryInfo对象就必须要从请求中提取数据封装。

注:下面代码中使用到了工具类的静态方法WebUtils.request2Bean,是将请求对象中的参数值转移到一个Bean对象中,该方法的实现具体请看《在WEB工程的web层中的编程技巧》。即使Request对象中没有我们需要的参数,那么创建出来的QueryInfo对象中的currentPage和pageSize属性我们在最开始创建时已经设置了默认值,所以无需担心空指针异常。

下面的代码对应流程图中的第一,二和第十、十一步骤:

 package com.fjdingsd.web.controller;
public class UserListServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try{
QueryInfo info = WebUtils.request2Bean(request, QueryInfo.class);
UserService userService = new UserServiceImpl();//通常使用工程模式获取实现类对象,这里为了简便直接采用实现类的构造器 PageBean page = userService.pageQuery(info);
request.setAttribute("pagebean", page);
request.getRequestDispatcher("/WEB-INF/jsp/userlist.jsp").forward(request, response);
}catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "查看用户失败");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
}
}

  上面在web层中已经使用Servlet将页面需要显示的信息全部封装进PageBean对象中,通过请求对象Request存储,最后转发进相应的JSP页面,这里例子为userlist.jsp页面,最后只要在这个页面中将请求对象中保存的PageBean对象提取出来,再将该对象中的每个属性的内容在页面相应的地方显示即可。

  在JSP页面中,我们以表格的形式将页面数据显示出来,除了用户想看的页面数据以外,其他的就是与页码相关的,因为在Servlet中我们将PageBean对象封装进请求Request对象中,所以在JSP页面中我们就可以通过EL表达式将其取出,而是会是大量地使用到EL表达式和JSP标签。

 <body>
<a href="${pageContext.request.contextPath}/servlet/UserListServlet">显示用户</a><br>
<table>
<tr>
<td>用户id</td>
<td>用户姓名</td>
<td>用户年龄</td>
</tr>
<c:forEach var="user" items="${requestScope.pagebean.contentData }">
<tr>
<td>${user.id}</td>
<td>${user.name}</td>
<td>${user.age}</td>
</tr>
</c:forEach>
</table>
<br>
<%--
共${pagebean.totalRecords} 条记录,每页${pagebean.pageSize}条,共${pagebean.totalPages}页,
当前第${pagebean.currentPage}页 &nbsp;&nbsp;
<a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">上一页</a>
<c:forEach var="bar" items="${requestScope.pagebean.pageBar}">
<a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">${bar}</a>
</c:forEach>
<a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">下一页</a>
--%> 共 ${pagebean.totalRecords} 条记录,
每页<input type="text" id="pagesize" value="${pagebean.pageSize }" onchange="gotopage(1)" style="width: 30px" maxlength="3">条,
共${pagebean.totalPages}页,
当前第${pagebean.currentPage}页 &nbsp;&nbsp;
<a href="javascript:void(0)" onclick="gotopage(${pagebean.previousPage})" >上一页</a> &nbsp;
<c:forEach var="bar" items="${requestScope.pagebean.pageBar}">
<a href="javascript:void(0)" onclick="gotopage(${bar})" >${bar}</a>&nbsp;
</c:forEach>
<a href="javascript:void(0)" onclick="gotopage(${pagebean.nextPage})" >下一页</a> &nbsp; 跳转<input type="text" id="forwardPage" value="${pagebean.currentPage}" style="width: 30px;" onchange="gotopage(this.value)">页 </body> <script type="text/javascript">
function gotopage(wantedPage) {
var pagesize = document.getElementById("pagesize").value;
window.location.href = "${pageContext.request.contextPath}/servlet/UserListServlet?currentPage="+wantedPage+"&pageSize="+pagesize; } </script>

  在上面的代码中,我们使用了JSTL标签库的<c:forEach>标签来迭代页面数据内容,也就是PageBean中的contentData集合。中间有一段的代码虽然被注释掉了,这里是用URL地址的方法给每个<a>标签中的href属性赋值超链接,在后面的代码中我使用的是JavaScript的方式。

  在Servlet中跳转到JSP页面的请求对象中设置了PageBean对象的关键字:request.setAttribute("pagebean", page); 因此在JSP中,使用EL表达式将以“pagebean”为关键字,而后面跟着PageBean对象的属性取出对应的值。

  在JavaScript中,上面无论是改变页面大小、上一页、下一页,某个特定页,跳转某页,都是根据gotopage方法来讲请求超链接发送给Servlet,再一步步发送到数据库查询。在gotopage方法中,传入参数是“wantedPage”,是用户想要去的页数,同时每次该方法调用还会获取页面大小“pagesize”,将这两个数放置在URL地址后作为请求参数给Servlet。

  最终效果如下:

  

  

JDBC使用数据库来完成分页功能的更多相关文章

  1. Mybatis Generator实现分页功能

    Mybatis Generator实现分页功能 分类: IBATIS2013-07-17 17:03 882人阅读 评论(1) 收藏 举报 mybatisibatisgeneratorpage分页 众 ...

  2. Mybatis分页-利用Mybatis Generator插件生成基于数据库方言的分页语句,统计记录总数 (转)

    众所周知,Mybatis本身没有提供基于数据库方言的分页功能,而是基于JDBC的游标分页,很容易出现性能问题.网上有很多分页的解决方案,不外乎是基于Mybatis本机的插件机制,通过拦截Sql做分页. ...

  3. 分页功能的实现——Jdbc && JSP

    @目录 什么是分页 ? 两个子模块功能的问题分析 和 解决方案 有条件查和无条件查询的影响 和 解决方案 项目案例: mysql + commons-dbutils+itcast-tools+Base ...

  4. python_way day18 html-day4, Django路由,(正则匹配页码,包含自开发分页功能), 模板, Model(jDango-ORM) : SQLite,数据库时间字段插入的方法

    python_way day18 html-day4 1.Django-路由系统   - 自开发分页功能 2.模板语言:之母板的使用 3.SQLite:model(jDango-ORM) 数据库时间字 ...

  5. Sybase数据库的分页功能

    项目中需要用到Sybase数据库的分页功能,想尽各种办法都没有成功,最后用如下的存储过程成功实现功能,记录备忘. ),@start int, @pageSize int as begin declar ...

  6. 利用PHP访问数据库——实现分页功能与多条件查询功能

    1.实现分页功能 <body><table width="100%" border="1">  <thead>    < ...

  7. [ Laravel 5.5 文档 ] 数据库操作 —— 在 Laravel 中轻松实现分页功能

     简介 在其他框架中,分页是件非常痛苦的事,Laravel 让这件事变得简单易于上手.Laravel 的分页器与查询构建器和 Eloquent ORM 集成在一起,并开箱提供方便的.易于使用的.基于 ...

  8. springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验--异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档---jpa访问数据库及page进行分页---整合redis---定时任务

    springboot学习-jdbc操作数据库--yml注意事项--controller接受参数以及参数校验-- 异常统一管理以及aop的使用---整合mybatis---swagger2构建api文档 ...

  9. spring和mybatis集成,自动生成model、mapper,增加mybatis分页功能

    软件简介 Spring是一个流行的控制反转(IoC)和面向切面(AOP)的容器框架,在java webapp开发中使用广泛.http://projects.spring.io/spring-frame ...

随机推荐

  1. nginx前端负载,后端apache获取真实IP设置

    原文链接: nginx前端负载,后端apache获取真实IP设置 参考文献: 前端Nginx,后端Apache获取用户真实IP地址  按照第二种方法设置不成功! 网站最前端是nginx,做的PROXY ...

  2. Windows(64位IIS)未在本地计算机上注册“Microsoft.Jet.OLEDB.4.0”提供程序

    在Windows 7(32位)用.Net开发的Excel导入数据表功能,测试后一切正常,站点发布挪到Windows Server 2008(64位)上就意外了,出现错误提示,运行程序,抛出异常:未在本 ...

  3. win32下利用python操作printer

    在win32下操作printer:   1)import win32print   2) 获得默认打印机名:          >>> win32print.GetDefaultPr ...

  4. if…else…if…else…

    参见以前做过的练习一元二次方程 #include <stdio.h> #include <math.h> /* 一元二次方程的标准形式:ax2+bx+c=0 a,b,c为常数, ...

  5. Delphi_MemoryModule — load DLL from memory. Also includes hooking utilities.

    https://github.com/Fr0sT-Brutal/Delphi_MemoryModule

  6. Kafka介绍

    本文介绍LinkedIn开源的Kafka,久仰大名了,依照其官方文档做些翻译和二次创作.相应能够查看整份官方文档. 基本术语 topics,维护的消息源种类(更像是业务上的数据种类/分类) produ ...

  7. 亚马逊带Marketplace product code的image无法再mount到其他镜像上

    这是对已发布镜像的保护么?难道对其进行修改的路彻底断掉了? 以volume形式attach也不行,dd成raw再读取也读不了,敢问路在何方呢 If a volume has an AWS Market ...

  8. sublime2 c++的一些使用配置

    1 下载安装好tdw gcc后,配置好环境变量后,配置sublime2. tools->build system-> new build system... 里面输入: { "c ...

  9. javascript每日一练(十三)——运动实例

    一.图片放大缩小 <!doctype html> <html> <head> <meta charset="utf-8"> < ...

  10. Python 第六篇(上):面向对象编程初级篇

    面向:过程.函数.对象: 面向过程:根据业务逻辑从上到下写垒代码! 面向过程的编程弊:每次调用的时候都的重写,代码特别长,代码重用性没有,每次增加新功能所有的代码都的修改!那有什么办法解决上面出现的弊 ...