`

构建自己的通用分页组件(上)

阅读更多

1. 需求:
   在实际项目开发中,分页是我们常见的操作,在一般数据展示的列表页,都会使用到数据分页。分页时,在每个页面上只需取得该页面展示的数据及列出其他的页码即可,这样可以以合适的粒度来获取页面展示的数据,避免不必要的数据的传输。
   在软件的分层构架中,实现一个供前后台交互用的分页组件,已成为每个项目必不可少的潜在需求。本文将在实际项目中分页需求的基础上,讨论并实现一个通用的分页组件,然后,可将其收录到自己的工具箱中,在不同的项目中导入复用即可。


2. 构建Page类:
   我们知道,在实际分页时,前台调用者在调用后台业务方法分页取得数据时,需告诉业务方法当前取第几页的数据以及每页取多少条数据;并希望返回的结果集中不仅包含当前页面所需要的数据列表,还要包含总数据量和总页面数。这样在前台展示的时候,就可以在展示当前页面数据时,又能将所有的分页信息展示给用户,以供用户作为访问其他页面数据的导航之用。因此,我们可以构造一个Page类,来表示分页信息,如下:

public class Page {
    // 当前页面的页数
    private int pageIndex;
    // 页面大小
    private int pageSize;
    // 数据总量
    private int totalData = -1;
    // 剩余数据量
    private int surplusData;
    // 页面总量
    private int totalPage;
    // 是否仅取第一页
    private boolean firstPage;
    // 是否可以工作。默认为false,只有设置了数据总量才为true。
    private boolean ready = false;
}

    该Page类包含了当前页数、每页的数据量、总数据量、剩余数据量、总页面量、是否仅取第一页以及该分页对象是否已经准备好,可以正常工作的标志。这样,调用者在调用后台业务方法进行分页查询数据时,只要构造一个指定当前页数pageIndex和页面大小pageSize的分页对象page,作为参数传递给后台业务方法即可。因此,根据实际需要,我们提供以下构造Page对象的构造方法:

// 默认当前页面页数为第一页
private static final int DEFAULT_PAGE_INDEX = 1;
// 默认页面大小为10
private static final int DEFAULT_PAGE_SIZE = 10;

/**
 * 以默认当前页面和页面大小构造一个分页对象。
 * 其中,默认当前页数为1,默认页面大小为10。
 */
public Page() {
    this.pageIndex = DEFAULT_PAGE_INDEX;
    this.pageSize = DEFAULT_PAGE_SIZE;
}

/**
 * 以指定的当前页面页数和页面大小构造一个分页对象。
 * @param pageIndex 当前页数,若参数值不大于0,则使用默认值1。
 * @param pageSize 页面大小,若参数值不大于0,则使用默认值10。
 */
public Page(int pageIndex, int pageSize) {
    this.pageIndex = pageIndex > 0 ? pageIndex : DEFAULT_PAGE_INDEX;
    this.pageSize = pageSize > 0 ? pageSize : DEFAULT_PAGE_SIZE;
}

/**
 * 以指定的页面大小构造一个表示第一页的分页对象。
 * @param pageSize 页面大小,若参数值不大于0,则使用默认值10。
 * @return 构造好的第一页分页对象。
 */
public static Page newFirstPage(int pageSize) {
    Page page = new Page(1, pageSize);
    page.setFirstPage(true);
    page.setTotalData(pageSize);

    return page;
}

   无参构造方法Page()将构造一个默认当前为第1页,每页取10条数据的分页对象;Page(int pageIndex, int pageSize)可以构造一个指定当前页数和页面大小的分页对象;newFirstPage(int pageSize)为一静态方法,此方法可以以指定的页面大小构造一个表示只取第一页数据的分页对象,这种方式将不会计算数据总量和页面总量,出返回第一页的数据外,不做任何处理,以满足我们在一些首页只取前几条数据的需求。
   至此,Page类的基本属性已定,下面我们看一下如何添加一些方法,利用这个Page类来实现前后台的交互。

 

3. 添加方法:
   从上面的需求分析中可知,根据调用者传递的page对象参数,后台业务方法应根据此参数查询出当前页面的数据列表、总数据量和总页面数返回给调用者。那么,我们抛开数据持久层具体实现技术不管,除了在底层采用分页技术查询出当前页面的数据外,如果调用者不是只取第一页,那么还应该查询出总的数据量,并设置到page对象中,由于知道了总数据量和每页显示的数据量,我们就能够计算出总页面数,剩余数据量等信息。因此,我们希望,在设置总数据量之后,page对象能够自动将这些信息计算好,所以我们在Page类中提供了设置总数据量的方法如下:

/**
 * 设置数据总量。在使用时,需提前调用此方法进行设置。
 * 当数据总量设置好之后,会计算页面总量、修正当前页面页数、计算剩余数据量,
 * 并设置该分页对象已经准备好,可以正常工作。
 * @param totalData 数据总量。
 */
public void setTotalData(int totalData) {
    this.totalData = totalData;

    this.totalPage = this.totalData / pageSize +
            (this.totalData % pageSize == 0 ? 0 : 1);

    if (this.pageIndex > this.totalPage) {
        this.pageIndex = this.totalPage;
    }

    this.surplusData = this.totalData - (this.pageIndex - 1) * this.pageSize;

    this.ready = true;
}

   在设置完总数据量之后,分页对象page就能正常工作了。持久层在设置完数据总量后,必须取得当前要查询数据的起始行号和结束行号,以便对数据库进行分页查询。因此,我们在Page类中还必须提供获取当前分页的页面范围的方法,如下:

/**
 * 获取当前分页对象的页面范围,包含当前页面的起始行和结束行。
 * 如果未先调用setTotalData()方法设置数据总量,则会抛出运行时异常。
 * @return 当前分页对象的页面范围。
 * @throws java.lang.IllegalArgumentException 异常。
 */
public PageScope getPageScope() throws IllegalArgumentException {
    if (!ready) {
        throw new IllegalArgumentException("Page has not seted data amount.");
    }

    if (this.pageIndex > this.totalPage) {
        return null;
    }

    PageScope scope = new PageScope();
    int startLine = (this.pageIndex - 1) * this.pageSize;
    int endLine;
    if (this.surplusData < this.pageSize) {
        endLine = startLine + this.surplusData - 1;
    } else {
        endLine = startLine + this.pageSize - 1;
    }
    if (startLine < 0) {
        startLine = 0;
    }
    if (endLine < 0) {
        endLine = 0;
    }
    scope.setStartLine(startLine);
    scope.setEndLine(endLine);

    return scope;
}

   PageScope类作为标识当前页面的页面范围信息。它只有两个属性:起始行号和结束行号。如下:

public class PageScope {
    // 起始行号
    private int startLine;
    // 结束行号
    private int endLine;
}

   这样,后台业务方法通过对指定页面数据的分页查询和对时局总量的设定,把调用者所需要的页面数据和其他页面信息包装到page对象中返回,就实现了我们通用的分页需求。


4. 如何使用?
   在介绍完分页类Page和页面范围类PageScope后,我们来看一下,如何使用自定义的分页组件来简化我们的实际开发。下面我们以分页查询用户列表为例,来说明我们自定义的通用分页组件的使用:
   业务层在用户业务接口UserService中提供的分页查询用户列表的方法queryUsers(Page page);

public List<User> queryUsers(Page page) throws ServiceException;

   调用者如果想查询第3页、每页查询15条用户信息,则可使用如下调用方式:

Page page = new Page(3, 15);
UserService userService = new UserServiceImpl();
List<User> usertList = userService.queryUser(page);

    这样调用者就取到了第3页15条的用户信息,而且此时的page对象中,已经包含了其他页面的信息,包括总页数,总数据量等,我们可根据这些信息来展示其他页码。
    如果只想取前5条数据,而不关心其他其他页面信息,则可使用:

Page page = Page.firstPage(5);
List<User> usertList = userService.queryUser(page);

   说明: 当不是分布式的应用时,前后台部署在同一个JVM上,这样通过前台传递的page对象的引用,后台设置好页面信息后,实际已经修改了前台传递的page对象,这样就不必再将page对象传回前台调用者。
   但是在分布式的环境中,后台在对page对象操作后,必须与结果集userList一起传递给调用者,并且Page类和PageScope类要实现序列化接口。

 

5. 测试:
   下面提供了一个简单的测试,来说明自定义的分页组件的使用正确性:

public class PageTest {

    /**
     * 测试构造一个仅表示第一页的分页对象。
     */
    @Test
    public void newFirstPage() {
        Page page = Page.newFirstPage(10);
        PageScope scope = page.getPageScope();

        assertTrue(page.isFirstPage());
        assertEquals(10, page.getTotalData());
        assertEquals(1, page.getTotalPage());
        assertEquals(1, page.getPageIndex());
        assertEquals(10, page.getPageSize());
        assertEquals(0, scope.getStartLine());
        assertEquals(9, scope.getEndLine());
    }

    /**
     * 测试构造一个指定当前页数和页面大小的分页对象。
     */
    @Test
    public void newPage() {
        Page page = new Page(2, 10);
        page.setTotalData(55);
        PageScope scope = page.getPageScope();

        assertFalse(page.isFirstPage());
        assertEquals(55, page.getTotalData());
        assertEquals(6, page.getTotalPage());
        assertEquals(2, page.getPageIndex());
        assertEquals(10, page.getPageSize());
        assertEquals(10, scope.getStartLine());
        assertEquals(19, scope.getEndLine());
    }
}

 

6. 相关说明:
   本文通过对实际项目中分页的需求,来构建一个通用的分页组件,以便在项目中复用并简化了项目中分页的开发工作。
   所有的源代码和测试代码已上传,仅供大家参考。
   如果有更好的建议和意见,还望大家不吝赐教,以便我改进,谢谢!
   在下文中我会再写一个自定义的Jsp分页标签,以便在页面中通用处理分页页码。

分享到:
评论
21 楼 深蓝luzlu 2011-10-22  
楼主辛苦,学习了
20 楼 xuyuanxi444 2011-07-21  
看了 还是不清楚 啊。。。。
19 楼 hnsyandy 2010-10-25  
不知道LZ的PageScope在此有何意义?
18 楼 finalerboy 2010-06-07  
我还想知道。

Action里面怎么拼参数,怎么取SIZE,DAO里怎么取总。。。

可否再详细一点?
17 楼 Wanghuidong 2010-05-29  
恩不错。。代码结构比较合理 借鉴一下
16 楼 pejuwe 2010-05-29  
还不错,看看!
15 楼 zhangshixi 2010-05-29  
<div class="quote_title">pengfeng 写道</div>
<div class="quote_div">没有看到楼主的分页组件里边包含当前页显示的数据,不知道楼主是怎么考虑当前页显示的数据的?</div>
<p><br>关于如何取得当前页面的数据,我在文章中已写的很清楚,请查看文章的<span style="color: #0000ff;">“如何使用”</span>部分。 <br>我也说过多遍,这里的分页只是提供一个供前后台方便交流使用的组件,至于如何处理当前页的数据、以及底层如何实现分页技术,都可根据具体实现的不同而不同,这样也大大提高了灵活和通用性,减少了代码的耦合性,给开发提供了便利的同时,也降低了代码的维护成本。</p>
<p> </p>
14 楼 pengfeng 2010-05-28  
没有看到楼主的分页组件里边包含当前页显示的数据,不知道楼主是怎么考虑当前页显示的数据的?
13 楼 zhangshixi 2010-05-28  
zhangqh_zz 写道
分页,程序部分例子很多,类似楼主的包装很多,但数据库部分如何处理也是很关键的
因为大量数据不可能一次取出,可能对于有些数据库有好的SQL,但有些可能没有
如DB2 只能row number()先生成序号,而oracle可能有自己的序号..........

这里的分页只是提供一个供前后台方便交流使用的组件,至于底层如何实现和优化,对于调用者来说应该是透明的吧。
12 楼 zhangqh_zz 2010-05-28  
分页,程序部分例子很多,类似楼主的包装很多,但数据库部分如何处理也是很关键的
因为大量数据不可能一次取出,可能对于有些数据库有好的SQL,但有些可能没有
如DB2 只能row number()先生成序号,而oracle可能有自己的序号..........
11 楼 jerry.yan.mj 2010-05-27  
精神可嘉!!!

但是这不是重复劳动吗?DisplayTag能够实现你的全部功能。
http://displaytag.sourceforge.net/1.2/index.html
10 楼 zhangshixi 2010-05-27  
dsea 写道
zhangshixi 写道
anzn20 写道
代码没给完!

代码我已打包上传上了,不知这位兄弟所说是何代码?

最好发布能直接导入的

兄弟,你也太懒了,我都一步一步写的那么详细了,你也就导入几个需要的jar包而已,我想这应该不是难事吧。如果我把所有都放上,势必给别人下载加大负担,如果你需要的话,可发信息给我,我发给你一份,请见谅。
9 楼 dsea 2010-05-27  
zhangshixi 写道
anzn20 写道
代码没给完!

代码我已打包上传上了,不知这位兄弟所说是何代码?

最好发布能直接导入的
8 楼 zhangshixi 2010-05-27  
anzn20 写道
代码没给完!

代码我已打包上传上了,不知这位兄弟所说是何代码?
7 楼 zhangshixi 2010-05-27  
xiangkun 写道
不知道 [下] 会不会有自定义的分页标签。。!!!
如果有的话,我期待看标签写的灵活性!

会有的,明天上...
6 楼 xiangkun 2010-05-27  
不知道 [下] 会不会有自定义的分页标签。。!!!
如果有的话,我期待看标签写的灵活性!
5 楼 anzn20 2010-05-27  
代码没给完!
4 楼 zhangshixi 2010-05-27  
naily 写道
lz讲解非常细致,清晰!赞一个

谢谢~希望通过分享,大家共同进步。
3 楼 naily 2010-05-27  
lz讲解非常细致,清晰!赞一个
2 楼 zhangshixi 2010-05-27  
caoyangx 写道
你看过springside吗?

只是大致了解,并未使用过,不知兄弟有何指教?

相关推荐

    LWC-Datatable:通用数据表(分页、选择和搜索)

    使用 LWC 的数据表 介绍 Lightning Web 组件 (LWC) 是使用 ... 在本文中,我们将专注于创建具有关键功能(如分页、行选择、静态标题和全局搜索)的通用数据表组件。 本文的源代码位于库中。 创建通用组件 首先,我们将

    vue-composable-Vue组成API的可组合组件。 i18n,验证,分页,提取等。+30个可变的可组合函数。-Vue.js开发

    vue-composable简介vue-composable即开即用,可以使用composition-api通用组件。 基于100%打字稿的可组合组件vue-composable简介vue-composable即开即用,可以使用composition-api通用组件。 100%基于打字稿的可...

    基于java的简单的教师-课程智能排课系统

    后端技术:Spring Boot 应用框架、MyBatis 通用 Mapper4、MyBatis 分页插件、Maven 项目构建管理、MyBatis Generator 代码生成Thymeleaf 模板引擎、Apache Shiro 安全框架、Logback 日志组件、Druid 数据库连接池、...

    Web应用前端技术的探索与实践

    6.5.2.18 分页组件Pagination 151 6.5.2.18.1 效果 151 6.5.2.18.2 参数说明 152 6.5.2.19 文件上传组件 153 6.5.2.19.1 uploadify 153 6.5.2.19.2 swfUpload 159 6.5.2.20 消息提示、弹出广告组件 162 6.5.2.20.1 ...

    backoffice:使用PETAL堆栈构建的管理工具

    我在每个管理页面上都重复了很多相同的事情(搜索,分页,表单组件,index.html等)。 它们是非常简单的页面。 那么为什么不重构呢? 我查看了重复的资料,将其提取出来,然后突然看起来它足够通用,足以成为一个...

    百度地图开发java源码-jbot:代码生成器(swagger+springboot+spring+mybatis)

    百度地图开发java源码 java项目生成器 ...项目构建管理 lombok 代码简化工具 logstash 日志管理 mapper 通用mapper pagehelper 分页插件 Freemarker 模板引擎 前端技术 技术 名称 jQuery 函式库 Boots

    springBootDemo

    使用Swagger2构建RESTful API 文档 RESTful API设计准则 swagger注解总结 快速上手 项目源码 Spring Boot 集成 MyBatis Mybatis原理简介 官方组件包使用 XML版本 注解版本 项目源码 第三方组件包使用 ...

    ASP.NET 控件的使用

    第一部分 构建ASP.NET页面 第1章 ASP.NET Framework概览 2 1.1 ASP.NET和.NET Framework 5 1.1.1 框架类库 5 1.1.2 公共语言运行库 9 1.2 ASP.NET控件 10 1.2.1 ASP.NET控件概览 11 1.2.2 HTML控件 12 1.2.3 理解...

    asp.net知识库

    优化后的通用分页存储过程 sql语句 一些Select检索高级用法 SQL server 2005中新增的排序函数及应用 根据基本表结构及其数据生成 INSERT ... 的 SQL 简便的MS SQL 数据库 表内容 脚本 生成器 将表数据生成SQL脚本的...

    flama:with使用此喷火器启动您的API

    Flama旨在之上带来了层提供一个易于学习和快速开发方法构建具有高性能GraphQL和REST的API。 与Starlette一样,Flama是开发异步和生产就绪服务的理想选择。 除其他特征外,它还提供以下功能: API资源的通用类,可...

    Spring面试题

    在 AOP 方式中,可以反过来将日志服务模块化,并以声明的方式将它们应用到需要日志的组件上。当然,优势就是 Java 类不需要知道日志服务的存在,也不需要考虑相关的代码。所以,用 Spring AOP 编写的应用程序代码是...

    基于J2EE框架的个人博客系统项目毕业设计论文(源码和论文)

    可以在管理页面上添加博文的分类,可以上传图片和游览自己的相册,在上传过程中可以将一张图片定义为自己的签名,在个人管理页面中注册用户还可以修改自己的个人信息。博文管理,友情链接管理及博文分类管理,用例图...

    Web开发敏捷之道-应用Rails进行敏捷Web开发-第三版.rar

    23.3 用于格式化、链接和分页的辅助方法 386 23.4 如何使用表单 393 23.5 包装模型对象的表单 393 23.6 自制表单构建器 403 23.7 处理与模型对象无关的字段 406 23.8 Rails应用的文件上传 409 23.9 布局与组件 411 ...

    JAVA上百实例源码以及开源项目

     通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到张三编码后的公钥,将其解码,李四用张三的公钥加密信息,并发送给李四,张三用自己的私钥解密从李四处收到的信息…… Java利用DES私钥对称加密代码实例 ...

    java开源包1

    GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以...

    Linux2.6内核标准教程(共计8-- 第1个)

    Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导过程;然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,...

    我记录网站综合系统 1.9.rar

    •所有 APP 和基础组件都可以自定义安装。 •界面修改方便,模板语法简单,可在线轻松自定义界面; •原生支持 Sqlserver、Access、Mysql 数据库; •支持多国语言(i18n); •支持静态资源(css/js/图片)和动态...

    Linux2.6内核标准教程(共计8--第6个)

    Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导过程;然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,...

    Linux2.6内核标准教程(共计8--第3个)

    Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先讲解Linux系统的引导过程;然后对Linux内核的3大核心模块——内存管理、进程管理、中断和异常处理进行了深入的分析; 在此基础上,...

    java开源包11

    GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以...

Global site tag (gtag.js) - Google Analytics