分页机制有两种: 一种是根据偏移量(或页码)+页大小获取数据,另一种是根据上一次看过的最后一条记录+页大小获取本页记录。
后一种机制常见于weibo/twitter这种feed流页面:记住当前页最后一条feed的发表时间,然后找出比这个时间更早的feed. 这种情况下一般不需要知道feed总数。
姑且把这种机制称为“基于比较的分页”。 使用这种机制时,需要注意的地方有:
1. 用作比较的维度应该是唯一的,在做大小比较时才不会遗漏数据。如果按ID来比较,则比id=1更大的记录是(2,3,…),不会遗漏; 如果按发表时间来比较,则比100毫秒更大的记录是(101, 102…), 而同在第100毫秒发表的其他记录就会被遗漏掉。
2. 除了往下翻页,还可能要往上翻页; 系统应该同时支持。
3. “首页”应该传什么值作为比较基准? 如果id为Long型,应该传-1还是传一个Long.maxValue()? 这种地方最容易埋坑,引发程序错误和沟通困难。 最好的办法是再另传一个参数:firstPage = true, 简单明了。
4. 服务端回传记录时,应该同时回传本页记录第一条和最后一条的比较值,比如第一个feed的id和最后一个 feed的id; 客户端/浏览器 再根据这两个记录决定上一页和下一页的URL。你会说不传也可以,客户端自己从feed列表中找出这两个id; 然而用于比较分页的维度未必是feed的属性,从feed列表中是找不出来的,比如分页查询“我评论过的feed”时,要通过评论ID来做比较,评论ID显然并不是feed的属性。
5. 第4点帮助你得出一个结论:分页参数可以跟业务无关,客户端不必知道分页维度的业务意义; 同理,也不必知道下一页的记录是比某个属性更“大”,还是更“小”,客户端只需要传“下一页”,或是“上一页”,让服务端自己决定取更大或是更小的记录。
6. 有了这个结论,我们可以做一个通用的、适用于任何业务分页参数类。示范代码:
分页参数类:
public class PageByCompareParam {
* 服务端将跟这个值比较。日期时间类型请传毫秒数时间戳,数字类型将转成字符串后再传过来
*/
private String value;
/**
* 页大小
*/
private int pageSize;
/**
* true = next page, false = previous page. 默认为true
*/
private boolean next = true;
/**
* 访问的是否是第一页,如果是的话,value将被忽略。默认为true
*/
private boolean firstPage = true;
...
}
结果参数类:
public class PageQueryResult {
/**
* 本页结果中的第一条记录的相关值。
*/
private String resultFirstValue;
/**
* 本页结果中的最后一条记录的相关值。
*/
private String resultLastValue;
}
7. 上述PageQueryResult.resultFirstValue和PageQueryResult.resultLastValue从哪里来? 为了解决这个问题,最好为你的每一条查询记录配一个sortOrder属性;当sortOrder不是记录本身的属性时,这一点尤其必要。所以,你内部使用的查询结果可以这样写:
public class PageByCompareQueryResult<D, S> {
/**
* 本页结果
*/
private List<ResultEntry<D, S>> resultList;
public S getFirstSortOrder() {
return resultList.get(0).getSortOrder();
}
public S getLastSortOrder() {
return resultList.get(resultList.size() - 1).getSortOrder();
}
public List<D> getDataList() {
......
}
/**
* 结果列表中的一个元素,由数据本身和用于比较用的排序号组成
*
*
* @param <D>
* @param <S>
*/
public static class ResultEntry<D, S> {
private D data;
private S sortOrder;
......
}
}
7. 最后附一段ibatis代码:
public List<Post> selectPosts(long idToCompareWith, int pageSize,
NumCompareFlag numCompareFlag) {
Map<String, Object> params = new HashMap<String, Object>();
params.put("id", idToCompareWith);
params.put("rowCount", pageSize);
params.put("numCompareFlag", numCompareFlag.name());
return getSqlMapClientTemplate().queryForList("selectPosts",
params);
}
public static enum NumCompareFlag {
/**
* 更大。
*/
GREATER,
/**
* 更小。
*/
LESS;
}
<select id="selectPosts" parameterClass="map" resultMap="resultMap"> select * from post where <include refid="id_compare"/> order by id desc LIMIT 0, #rowCount# </select> <sql id="id_compare"> <isEqual prepend="and" property="numCompareFlag" compareValue="LESS"> id < #id# </isEqual> <isEqual prepend="and" property="numCompareFlag" compareValue="GREATER"> id > #id# </isEqual> </sql>