分页查询/迭代器食谱(paginated queries / iterator recipe)

2019-09-22 20:50发布

我看到这种模式很多。

在服务器:

// Get a bounded number of results, along with a resume token to use 
// for the next call. Successive calls yield a "weakly consistent" view of 
// the underlying set that may or may not reflect concurrent updates.
public<T> String getObjects(
        int maxObjects, String resumeToken, List<T> objectsToReturn);

在客户端:

// An iterator wrapping repeated calls to getObjects(bufferSize, ...)
public<T> Iterator<T> getIterator(int bufferSize);

多数地方推出自己的这两种方法的版本,并实现是相当困难得到的权利。 有很多边缘情况的错误。

是否有一个规范的配方或库这些查询?

(你可以为服务器端存储一些简单的假设,例如,T有一个自然顺序)。

Answer 1:

下面是一个使用AbstractIterator从谷歌,番石榴库和弹簧JDBC实际查询数据库:

public Iterable<T> queryInBatches(
        final String query,
        final Map<String, Integer> paramMap,
        final int pageSize, final Class<T> elementType) {
    return new Iterable<T>() {
        @Override
        public Iterator<T> iterator() {
            final Iterator<List<T>> resultIter = 
                    queryResultIterator(query, paramMap, pageSize, elementType);

            return new AbstractIterator<T>() {
                private Iterator<T> rowSet;

                @Override
                protected T computeNext() {
                    if (rowSet == null) {
                        if (resultIter.hasNext()) {
                            rowSet = resultIter.next().iterator();
                        } else {
                            return endOfData();
                        }
                    }

                    if (rowSet.hasNext()) {
                        return rowSet.next();
                    } else {
                        rowSet = null;
                        return computeNext();
                    }
                }};
        }};
}


private AbstractIterator<List<T>> queryResultIterator(
        final String query, final Map<String, Integer> paramMap, 
        final int pageSize, final Class<T> elementType) {
    return new AbstractIterator<List<T>>() {
        private int page = 0;

        @Override
        protected List<T> computeNext() {
            String sql = String.format(
                    "%s limit %s offset %s", query, pageSize, page++ * pageSize);
            List<T> results = jdbc().queryForList(sql, paramMap, elementType);
            if (!results.isEmpty()) {
                return results;
            } else {
                return endOfData();
            }
        }};
}

AbstractIterator隐藏了大部分涉及编写自己的实现并发症的Iterator 。 你只需要实现computeNext方法,无论是在迭代器返回的下一个值或来电endOfData指示有在迭代器没有更多的价值。



Answer 2:

下面是一些对我的作品。 它还使用AbstractIterator从谷歌,番石榴库,但需要Java8流的优势,简化实现。 它返回T类型的元素的Iterator

Iterator<List<T>> pagingIterator = new AbstractIterator<List<T>>() {
    private String resumeToken;
    private boolean endOfData;

    @Override
    protected List<T> computeNext() {
        if (endOfData) {
            return endOfData();
        }

        List<T> rows = executeQuery(resumeToken, PAGE_SIZE);

        if (rows.isEmpty()) {
            return endOfData();
        } else if (rows.size() < PAGE_SIZE) {
            endOfData = true;
        } else {
            resumeToken = getResumeToken(rows.get(PAGE_SIZE - 1));
        }

        return rows;
    }
};

// flatten Iterator of lists to a stream of single elements
Stream<T> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(pagingIterator, 0), false)
    .flatMap(List::stream);

// convert stream to Iterator<T>
return stream.iterator();

另外,也可以通过使用以下述方式方法参照返回一个可迭代:

// convert stream to Iterable<T>
return stream::iterator;


文章来源: paginated queries / iterator recipe