分页查询包装成迭代器或流返回
分页查询要求调用者传递页码和页大小信息,是为了解决数据量太大而消费端无法一次性接纳这么多数据时所采用的技术手段。
对于具有“页”概念的消费端来说,采用分页查询和加载数据的确便捷且有效,但是某些场景下,调用者其实并不想关心分页查询这种具体的技术方式,只关心还有没有数据待处理,此时如果再让调用者提供页码和页大小信息就显得不够便捷。此时,可以考虑将分页查询过程包装成迭代器或流对象返回给调用者。
为了便于复用,抽象成接口,代码参考如下:
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
/**
* 将分页查询过程包装成迭代器或流对象返回
*
* @param <T> 查询结果的类型
* @param <C> 查询条件的类型
*/
public interface IterableQuery<T, C> {
/**
* 查询数据
*
* @param condition 查询条件
* @return 可供遍历查询结果的迭代器
*/
default Iterator<T> iteratorOf(C condition) {
//构造一个迭代器并返回
return new Iterator<T>() {
//用于临时存放一页查询结果的迭代器
private Iterator<T> iterator = null;
//用于分页查询时的当前页码
private AtomicInteger page = new AtomicInteger(0);
@Override
public boolean hasNext() {
//先判断当前页是否还有元素,如果没有则尝试加载下一页数据,如果下一页数据为空才返回false
if (iterator != null && iterator.hasNext()) return true;
//否则,尝试加载下一页数据,并重置迭代器
List<T> tmpPage = listByPage(condition, page.incrementAndGet());
iterator = tmpPage.iterator();//重置迭代器
return iterator.hasNext();
}
@Override
public T next() {
if (iterator == null) {
hasNext();
}
T rt = null;
try {
rt = iterator.next();
} catch (NoSuchElementException e) {
//如果取不到时,需要尝试加载一次。确保在未调用hasNext方法而是直接调用next方法时当前页数据正好取完的情况
if (hasNext()) {
rt = iterator.next();
} else {
throw e;
}
}
return rt;
}
};
}
/**
* 查询数据
*
* @param condition 查询条件
* @return 可供遍历查询结果的流对象
* @throws NoMoreStreamElementException 当流中没有更多元素时抛出此异常
*/
default Stream<T> streamOf(C condition) throws NoMoreStreamElementException {
Iterator<T> iterator = iteratorOf(condition);
Stream<T> stream = Stream.generate(() -> {
if (iterator.hasNext()) {
return iterator.next();
} else {
throw new NoMoreStreamElementException();
}
});
return stream;
}
/**
* 始终按照固定页大小来分页查询数据
*
* @param condition 查询条件
* @param page 页码,从1开始
* @return 若该页无数据则返回元素个数为0的列表对象
*/
List<T> listByPage(C condition, int page);
/**
* 流中没有更多元素时抛出此异常
*/
class NoMoreStreamElementException extends RuntimeException {
}
}