文章目录
饱经定时任务折磨。倒不是不会写定时任务,还是这个看似简单,实际有一些细节。
定时任务查出的条数可能不同,如何进行控制呢。
数据主要有几类:
纯查询数据
状态变化数据。
状态不变数据。
for循环
for循环是最基础的循环语句,每只程序猿对它都不陌生。
语法结构:
for(单次表达式;条件表达式;末尾循环体)
{
中间循环体;
}
最简单的for循环
for(;;){}
是的,你没看错,只有分号,表达式和循环体都是空的。这样也是正确的表达式,相当于无限循环。
表达式扩展(非常强大)
单次表达式扩展
单次表达式支持多条语句,,
分隔即可。
for (int j =0,int k=5,int d=ddd();
j<10 ;
j++ ){
}
注:单词表达式定义变量,不可以引用外层变量,但是可以调用外层函数。
实测应该也不能定义复杂类型。只能是int类型。
条件表达式扩展
条件表达式还可以加些自己的逻辑,而且不只一个条件,用逻辑符号连接即可。
例如:循环中,页数>0 且 循环次数<1000(避免无限循环)。
for (int j =0; pages>0 && j<1000 ;j++ ){
}
注:条件表达式好像是定义不了多个变量,但是多个逻辑表达式可以共同作用,也是够用了。
末尾循环体扩展
和单次表达式相反,末尾循环体不支持变量定义,但是可以直接使用变量。
包括:单次表达式定义的变量,
纯查询数据
这样的比较好处理,最简单的处理方法,就是先查一次总分页信息。
然后按分页循环处理即可。代码:
// 先查询一次
PageHelper.startPage(request.getPageNum(), request.getPageSize());
List<User> Users = UserMapper.selectByExample(request);
PageInfo<User> pageInfo = new PageInfo<>(Users);
for (int i=0;i<pageInfo.getPages();i++) {
int pageNum = pageInfo.getNavigatepageNums()[i];
request.setPageNum(pageNum);
List<User> Users = UserMapper.selectByExample(request);
}
这样功能没问题,就是看起来乱乱的,尤其是主逻辑代码比较多的时候。
纯查询数据优化后的代码
主要还是利用for循环的强大功能。代码:
User request = new User();
PageInfo<User> pageInfo = new PageInfo<>();
for (int i=0,pages=1;
i<pages;
i++,request.setPageNum(i+1),pages=pageInfo.getPages()) {
}
for循环之所以这么写,主要是为了易读。
状态可变数据(最优解)
每循环一次之后状态会变化,或者次数+1,或者其他标记值改变。
while进行控制
while (true){
List<Entity> list= service.selectList();
log.info("本次查出的数据:{}", JSONObject.toJSONString(list));
if(list==null || list.size()==0){
log.info("未查出数据结束任务");
return ;
}
for(Entity item:list){
// 业务逻辑
}
}
这段代码通常来说没问题,但是也有注意点。
那就是循环的数据要保证状态变更。
即运行过一次后,一定要确保改变信息使下一次相同条件无法再查到。
否则就会出现无限循环的问题。
例如,这次查到了,判断通过后,修改状态。但是因为判断条件始终不通过,状态一直没修改,那么这个while永远跑不完了,下次定时任务也永远无法执行。
另外,最好可以加上个计数器,记录循环的次数,可以做到心里有数。
状态不变数据
状态不进行任何改变,例如获取某个时间段的数据,进入到备份表。
因为没有状态改变,所以必须让每次的数据不同,否则就无限循环了。
可以pageNo的改变,超过pages就结束。 需要注意的是,一定要加分页信息。
注:状态不变的数据实际是有问题的,因为同条件可能会无限循环。
解决方案:
1、for循环保证每次都自增。
2、可以引入redis避免重复操作。(特别是云端对次数有限制的数据)
while(true) 或者 while(flag) 或者 do while
其实都没太大区别。
while(true)写法
基本可以作为固定模板了。
这种写法,退出条件在代码中定义即可。
代码:
// 设置页码数
int pageNo = 0;
// 设置每页的数量
int pageSize = 100;
while(true){
pageNo++;
queryParam.setPageNo(pageNo);
queryParam.setPageSize(pageSize);
logger.info("循环次数:{}",pageNo);
PageHelper.startPage(queryParam.getPageNo(), queryParam.getPageSize());
List<User> queryResult = mapper.selectList(queryParam);
PageInfo<User> pageInfo = new PageInfo<>(queryResult);
logger.info("pageInfo简化版:{}",pageInfo.toStringSimple());
int pages = pageInfo.getPages();
// 100条 1页
// 101条 2页
if(pageNo>pages){ // 如果页码>总页数 结束循环
break;
}
// 业务代码
}
pageInfo.toStringSimple()是为了打印出pageInfo除了list的信息,因为list信息比较多,不推荐打印,这里重写下PageInfo类即可。
do while写法
肯定也是可以的,pageNo和pages开始没有,给一个初始值,后面再赋值即可。
for用法
遇到个问题,在while中,setPage()无效,debug发现setPage是个空,这就导致永远都是操作第一页,肯定有问题啊,排查半天没查出来,不纠结了,改用for循环。
机制也比较简单,就是先查询总分页信息,然后再来个循环进行后续操作。
优点:机制容易掌握,for循环实际比while简单点,毕竟不用退出条件。
缺点:代码比较丑陋,毕竟先查一次只是为了分页。后续再循环,相当于写了两部分代码。
但是实测效果是可以的,代码丑就丑点吧。
后来又想了想,pages作为退出条件,可以赋一个值,然后在查询出来后,再赋实际值,就可以解决代码丑陋的问题了。
case:
public static void main(String[] args) {
int pages=1;
for (int j =0; j < pages; j++) {
System.out.println("第"+j+"次查询");
// pages重置 这里factPage就是业务查出的page
Integer factPages=2;
if(!ObjectUtils.isEmpty(factPages)&&factPages>1){
pages=factPages;
}
}
}
总结
总的来说,就是一定要避免无限循环,可以通过页码比对,也可以通过条数比对。
如果条数不多,懒一点可以不用循环,只查一次,条数大些,例如10000条,基本也够用。
无限循环的危害
这样任务永远跑不玩,不但对性能有压力,后续的任务也不会再跑了,很坑。