分页的那些事儿

分页的那些事儿

博客分类:
System Design

分页接口设计

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

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

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

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

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

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

分页工具可以是一个简单的计算工具,连实际的数据都不需要给它,只需要指定总数和每页大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

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”参数的值。这是一种最纯粹的分页工具了,实现也非常简单,下面我们来把它弄复杂一点。

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

class PaginationSupport<T> {
//构造器
public PaginationSupport(List<T> items, int pageSize){}

//其它主要方法不变
... ...
}

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

可以。引入这样一个接口:
1
2
3
4

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

而PaginationSupport这个类统一成如下形式:
1
2
3
4
5
6
7
8
9

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

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

... ...
}

这样一来,如果是内存分页,就在DataCollection接口的实现中,操纵这个数据list:
1
2
3
4
5
6
7
8
9

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:
1
2
3
4
5
6
7
8
9
10
11

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接口就会变成这个样子:
1
2
3
4

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

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

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

User_age=18_start=3_end=7

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

com.company.xxx.User.list=30000

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

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

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

文章系本人原创,转载请注明作者和出处(http://www.raychase.net)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值