??这个专栏之前定下的计划是每周会发布一篇,但现在距离上一篇文章的发布已经过去了3个多月的时间了。希望自己从这周开始接下来坚持每周至少输出一篇文章!欢迎各位同学朋友们积极监督!
??好,废话少说!现在开始进入正题。这次教给大家的开发技巧就是学会使用while或do-while这两种循环结构进行分页查询,这个分页查询的方式主要包括了查询数据库和调用接口这两种方式。可能这么说大家未必明白,没关系,接下来听我详细讲解。
??比如有个业务需求是这样的:将系统A中所有用户的信息上报到B系统中进行数据分析。假设系统A的用户表为t_user(主键字段:id),系统B提供了一个名为analyzeUserInfo(List users)这么一个接口(UserInfo类的属性信息跟t_user表中的字段一一对应)。那么你是如何做的呢?有些同学可能想到了一个直接粗暴的方法也就是把t_user表中的数据全部查出来,然后在代码中进行处理。这种方法也不是不可以,但是如果t_user这张表的数据量是千万级别的话,你依然选择把全部数据放在内存中处理吗?答案当然不是这样。因为这么多的数据量放在内存中操作的话大概率会引发OOM异常的。所以接下来我就讲解下我是怎么处理的。
- 分批处理。 首先我们可以针对t_user进行分页,不建议使用limit ?,?这种方式进行分页。为什么呢?因为阿里巴巴开发手册中已经说得很明白:
MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
所以得换种分页的方式,我们可以这样进行分页:select * from t_user where id > #{maxId,jdbcType=BIGINT} order by id asc limit #{size,jdbcType=BIGINT} 。通过order by id从小到大进行分页查询,首先maxId的值初始化为0,然后第二次循环的maxid = 0 + size, 接着以此类推,直到查询出的表t_user信息为空或者查询记录数小于每页的大小size时就跳出循环。
2.如果以上还是看不懂,没关系!接下来我直接上个伪代码,大家可以细品下,如下所示: 第一种方法:使用while循环
public void handleUserInfo() {
int maxId = 0;
int size = 500;
while(true) {
List<User> userList = userService.getUsers(maxId, size);
if(userList == null || userList .isEmpty()) {
break;
}
analyzeUserInfo(userList);
if(userList.size() < size) {
break;
}
maxId = userList.get(userList.size() - 1).getId();
}
第二种方法:使用do-while循环
public void handleUserInfo() {
int maxId = 0;
int size = 500;
List<User> userList;
do {
userList = userService.getUsers(maxId, size);
if(userList != null && !userList.isEmpty()) {
analyzeUserInfo(userList);
maxId = userList.get(userList.size() - 1).getId();
}
} while(userList != null && !userList.isEmpty() && userList.size() == size);
}
??因为代码中已经写得比较清楚啦,所以我就不做过多的文字解释了。相信认真读过上面代码的同学已经知道我想表达的的意思啦。当然如果再细致点的话,我们可以对analyzeUserInfo(userList)做个try-catch处理,或者在analyzeUserInfo方法内部做处理也没问题,这样做的目的就是避免一次循环的报错导致的整个循环的退出。
??今天下午跟一位同事讨论时他指出我这种while或do-while的写法可能会有死循环的风险,但我觉得只要我们控制好偏移量maxId和每页大小size,就不会出现死循环的问题。同时他还指出了如下的写法,也就是先计算出userList的总页数,然后使用for循环进行处理,伪代码如下所示:
第三种方法:使用for循环
public static int getPage(Integer size, int step) {
int page = size / step;
page += size % step > 0 ? 1 : 0;
return page;
}
public void handleUserInfo() {
int maxId = 0;
int size = 500;
Integer totalSize = userService.count();
Integer totalPage = getPage(totalSize, 500);
for(int i = 0; i < totalPage; i++) {
List<User> userList = userService.getUsers(maxId, size);
analyzeUserInfo(userList);
maxId = userList.get(userList.size() - 1).getId();
}
}
??本人感觉这个方法也是不错的!哈哈,看来多跟别人讨论还是对自己非常有帮助的!以上的三个方法中,本人推荐使用第一种方法:使用while循环,因为个人感觉这种方法会更直观并更好理解点。
??好,完毕!
|