Skip to content

四火的唠叨

一个纯正程序员的啰嗦

Menu
  • 所有文章
  • About Me
  • 关于四火
  • 旅行映像
  • 独立游戏
  • 资源链接
Menu

分页的那些事儿

Posted on 10/13/201210/08/2024 by 四火

1 最近同事在讨论一个关于分页的话题,我在此简单整理一下对于分页的认识。

首先,分页是什么层面上的事儿?是数据访问层面、业务层面还是展示层面?

对于数据访问层来说,具体说,对于查询接口,需要一个“from” 参数和一个“to” 参数,就可以做到获取查询结果集中特定的记录了,它不应该知道任何关于第几页和每页有几条数据这样的信息,这种信息应该是在上层的展示层面所关心的。

举例来说,有这样的接口调用(这只是其中一种接口形式,关于 DAO 接口的形式可以参见这篇文章的讨论):

map.put("age", 18);
map.put("from", 3);
map.put("to", 5);
List<User> userList = userDao.list(map);

其次,可以达成共识的是,分页功能应该由一个工具来实现,这个工具不应该知道任何业务逻辑,应该与其它部分解耦开。

分页工具可以是一个简单的计算工具,连实际的数据都不需要给它,只需要指定总数和每页大小:

class PaginationSupport {
	//构造器
	public PaginationSupport(int totalCount, int pageSize){}
	
	//翻页
	public void turnToNextPage(){}
	public void turnToPreviousPage(){}
	public void setPageNo(int pageNo){}

	//数据访问层需要的 start 和 end
	public int getStart(){}
	public int getEnd(){}

	//是否还有上一页、下一页
	public boolean hasNextPage(){}
	public boolean hasPreviousPage(){}

	//当前在哪一页
	public int getPageNo(){}

	... ...
}

翻页任意次之后,调用 getStart/getEnd 方法就能输出 DAO 需要的“start” 和“end” 参数的值。这是一种最纯粹的分页工具了,实现也非常简单,下面我们来把它弄复杂一点。

如果在内存里分页,借助模板参数,稍稍修改一下成下面这种形式,把数据给它,让它来帮你返回显示页需要的结果集合:

class PaginationSupport<T> {
	//构造器
	public PaginationSupport(List<T> items, int pageSize){}
	
	//其它主要方法不变
	... ...
}

可是,这种形式需要置入一个 List<T>,如果我想在数据库分页,而非内存分页,这种形式就做不到了。那能不能提供一种兼容二者的通用方式呢?

可以。引入这样一个接口:

interface DataCollection<T>{
	public List<T> getData(int start, int end);
	public int getCount();
}

而 PaginationSupport 这个类统一成如下形式:

class PaginationSupport<T> {
	//构造器
	public PaginationSupport(DataCollection<T> dc, int pageSize, int startPageNo){}

	//获取当前页数据
	public List<T> getData(){}

	... ...
}

这样一来,如果是内存分页,就在 DataCollection 接口的实现中,操纵这个数据 list:

PaginationSupport ps = new PaginationSupport<User>(new DataCollection<User>() {
	List<User> list = xxx;
	public int getCount() {
		return list.size();
	}
	public List<Object> getData(int start, int end) {
		return list.subList(start, end);
	}
}, 10);

而如果是需要 DAO 层 start 和 end 参数传入的分页,则在 DataCollection 接口的实现中,调用 DAO:

PaginationSupport ps = new PaginationSupport<User>(new DataCollection<User>() {
	public int getCount() {
		return userDao.count();
	}
	public List<User> getData(int start, int end) {
		... ...
		map.put("start", start);
		map.put("end", end);
		return userDao.list(map);
	}
}, 10);

需要补充的是,如果只需要下面这种调用 DAO 接口来实现的查询分页,分页的工作还可以进一步改进,因为将“start” 和“end” 这两个参数注入 map 的过程,完全可以让框架来完成—— 从分页工具开始,直到数据库访问的 SQL 代码为止,开发人员都可以不关心这两个参数,让框架来拼接这个查询子句。这样的话,DataCollection 接口就会变成这个样子:

interface DataCollection<T>{
	public List<T> getData(Map queryMap);
	public int getCount();
}

其中 getData 方法的 queryMap 参数,分页工具已经给预置好了一些协助查询的参数,开发人员不需要手动构造和添加这样的参数了。

好处仅仅是这么多吗?不是。分页工具只是做分页这一件事没错,但是框架可以利用它,在外面做很多额外的事情。比如,在接口改成如上的形式时,我们还可以做到对分页查询结果的缓存完全透明化,开发人员连缓存条目的 key 都不需要提供。如果我们规约好查询方法都叫“list”,框架获知当前查询的模型 T,完整查询的 queryMap 对象,以这两个作为生成 key 的因子(如果该模型的查询方法不止一个,那就还需要查询方法名也作为因子提供),比如生成了这样一个 key:

User_age=18_start=3_end=7

之后就可以根据一定的配置帮助缓存起查询的结果来,比如:

com.company.xxx.User.list=30000

表示 User 的查询缓存记录的过期时间是 30000 毫秒。

最后来说说查询子句不一致的问题。分页查询的 SQL 子句大不相同,比如 Oracle 会用到 rownum,而 MySQL 又需要 limit,所以一种方法是在 DAO 层屏蔽这样的差异。

不过,还有一个思路是利用 JDBC 来屏蔽差异,它提供了结果集对象,很适合我们做这件事(java.sql.ResultSet 接口的 next 和 previous 方法),比如我们可以自己定义一个名为 PageableResultSet 这样的接口来继承自 ResultSet 接口。不过需要注意的是,这个接口可没有提供跳到任意记录的方法,因此在实现的时候只能借由游标走,一行一行记录地 next 或者 previous。我见过的几个项目都没有用这个方式来实现的,关于它的优劣,欢迎你来给我分析分析。

——————————————————————————————————————–

2012 年 11 月 13 日:

momo 回复给出了一个分页方式讨论的链接,其中提到一种方式是“ 二次 top”,其实这也并没有什么特别的,从一个查询的结果集合中再取数据:

set @sqlstr = 'SELECT TOP ' + Str(@pagesize) + ' * from r_student where id not in';
set @sqlstr = @sqlstr + '(SELECT TOP '+ Str((@currentpage-1)*@pagesize) + ' id from  r_student order by Id)'

还有就是存储过程了。

文中给出的测试结论是,还是使用结果集的游标移动来实现分页获取数据的方法是最快的。

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接 《四火的唠叨》

×Scan to share with WeChat

你可能也喜欢看:

  1. EasyMock、EasyMock Class Extension 和 PowerMock
  2. 泛型趣谈
  3. C++学习杂记
  4. 画圆画方的故事
  5. 动手实现随机验证码

1 thought on “分页的那些事儿”

  1. momo says:
    11/13/2012 at 12:11 PM

    http://www.cnblogs.com/eaglet/archive/2008/10/09/1306806.html
    ResultSet 这个看起来和 DataReader 有点像啊。。
    在.net 里面似乎这种方式可取。
    我觉得可以分开来,先区 top n,然后超过这个 n 的再把 top 加倍,
    比如对于某个条件 x
    第一次查询的时候 sql 为 select top n xx from table where x
    用 datareader 去 某 page 内的数据
    然后翻页翻到超过 n 时
    为 select top 2n xx from table where x
    。。。。。

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

订阅·联系

四火,啰嗦的程序员一枚,现居西雅图

Amazon Google Groovy Hadoop Haskell Java JavaScript LeetCode Oracle Spark 互联网 亚马逊 前端 华为 历史 同步 团队 图解笔记 基础设施 工作 工作流 工具 工程师 应用系统 异步 微博 思考 技术 数据库 曼联 测试 生活 眼界 程序员 管理 系统设计 缓存 编程范型 美股 英语 西雅图 设计 问题 面向对象 面试

分类

  • Algorithm and Data Structure (30)
  • Concurrency and Asynchronization (6)
  • System Architecture and Design (43)
  • Distributed System (18)
  • Tools Frameworks and Libs (13)
  • Storage and Data Access (8)
  • Front-end Development (33)
  • Programming Languages and Paradigms (55)
  • Testing and Quality Assurance (4)
  • Network and Communication (6)
  • Authentication and Authorization (6)
  • Automation and Operation Excellence (13)
  • Machine Learning and Artificial Intelligence (6)
  • Product Design (7)
  • Hiring and Interviews (14)
  • Project and Team Management (14)
  • Engineering Culture (17)
  • Critical Thinking (25)
  • Career Growth (57)
  • Life Experience and Thoughts (45)

推荐文章

  • 聊一聊分布式系统中的时间
  • 谈谈分布式锁
  • 常见分布式系统设计图解(汇总)
  • 系统设计中的快速估算技巧
  • 从链表存在环的问题说起
  • 技术面试中,什么样的问题才是好问题?
  • 从物理时钟到逻辑时钟
  • 近期面试观摩的一些思考
  • RSA 背后的算法
  • 谈谈 Ops(汇总 + 最终篇):工具和实践
  • 不要让业务牵着鼻子走
  • 倔强的程序员
  • 谈谈微信的信息流
  • 评审的艺术——谈谈现实中的代码评审
  • Blog 安全问题小记
  • 求第 K 个数的问题
  • 一些前端框架的比较(下)——Ember.js 和 React
  • 一些前端框架的比较(上)——GWT、AngularJS 和 Backbone.js
  • 工作流系统的设计
  • Spark 的性能调优
  • “残酷” 的事实
  • 七年工作,几个故事
  • 从 Java 和 JavaScript 来学习 Haskell 和 Groovy(汇总)
  • 一道随机数题目的求解
  • 层次
  • Dynamo 的实现技术和去中心化
  • 也谈谈全栈工程师
  • 多重继承的演变
  • 编程范型:工具的选择
  • GWT 初体验
  • java.util.concurrent 并发包诸类概览
  • 从 DCL 的对象安全发布谈起
  • 不同团队的困惑
  • 不适合 Hadoop 解决的问题
  • 留心那些潜在的系统设计问题
  • 再谈大楼扔鸡蛋的问题
  • 几种华丽无比的开发方式
  • 我眼中的工程师文化
  • 观点的碰撞
  • 谈谈盗版软件问题
  • 对几个软件开发传统观点的质疑和反驳
  • MVC 框架的映射和解耦
  • 编程的未来
  • DAO 的演进
  • 致那些自嘲码农的苦逼程序员
  • Java 多线程发展简史
  • 珍爱生命,远离微博
  • 网站性能优化的三重境界
  • OSCache 框架源码解析
  • “ 你不适合做程序员”
  • 画圆画方的故事

近期评论

  • Ticket: TRANSACTION 1.922915 BTC. Go to withdrawal >> https://yandex.com/poll/enter/BXidu5Ewa8hnAFoFznqSi9?hs=20bd550f65c6e03103876b28cabc4da6& on 倔强的程序员
  • panshenlian.com on 初涉 ML Workflow 系统:Kubeflow Pipelines、Flyte 和 Metaflow
  • panzhixiang on 关于近期求职的近况和思考
  • Anonymous on 闲聊投资:亲自体验和护城河
  • 四火 on 关于近期求职的近况和思考
  • YC on 关于近期求职的近况和思考
  • mafulong on 常见分布式基础设施系统设计图解(四):分布式工作流系统
  • 四火 on 常见分布式基础设施系统设计图解(八):分布式键值存储系统
  • Anonymous on 我裸辞了
  • https://umlcn.com on 资源链接
© 2025 四火的唠叨 | Powered by Minimalist Blog WordPress Theme