Add WindowIterator and rename Scroll to Window.
The intend of WindowIterator is to support users who need to iterate multiple windows. It keeps track of the position and loads the next window if needed so that the user does not have to interact with the position at all. Also remove the Window methods to get the first/last position and enforce the index based variant. Update the documentation to make use of the newly introduced API. See: #2151 Original Pull Request: #2787
This commit is contained in:
@@ -23,65 +23,66 @@ import java.util.function.IntFunction;
|
||||
import org.springframework.data.util.Streamable;
|
||||
|
||||
/**
|
||||
* A scroll of data consumed from an underlying query result. A scroll is similar to {@link Slice} in the sense that it
|
||||
* contains a subset of the actual query results for easier consumption of large result sets. The scroll is less
|
||||
* A set of data consumed from an underlying query result. A {@link Window} is similar to {@link Slice} in the sense
|
||||
* that it contains a subset of the actual query results for easier consumption of large result sets. The window is less
|
||||
* opinionated about the actual data retrieval, whether the query has used index/offset, keyset-based pagination or
|
||||
* cursor resume tokens.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 3.1
|
||||
* @see ScrollPosition
|
||||
*/
|
||||
public interface Scroll<T> extends Streamable<T> {
|
||||
public interface Window<T> extends Streamable<T> {
|
||||
|
||||
/**
|
||||
* Construct a {@link Scroll}.
|
||||
* Construct a {@link Window}.
|
||||
*
|
||||
* @param items the list of data.
|
||||
* @param positionFunction the list of data.
|
||||
* @return the {@link Scroll}.
|
||||
* @return the {@link Window}.
|
||||
* @param <T>
|
||||
*/
|
||||
static <T> Scroll<T> from(List<T> items, IntFunction<? extends ScrollPosition> positionFunction) {
|
||||
return new ScrollImpl<>(items, positionFunction, false);
|
||||
static <T> Window<T> from(List<T> items, IntFunction<? extends ScrollPosition> positionFunction) {
|
||||
return new WindowImpl<>(items, positionFunction, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link Scroll}.
|
||||
* Construct a {@link Window}.
|
||||
*
|
||||
* @param items the list of data.
|
||||
* @param positionFunction the list of data.
|
||||
* @param hasNext
|
||||
* @return the {@link Scroll}.
|
||||
* @return the {@link Window}.
|
||||
* @param <T>
|
||||
*/
|
||||
static <T> Scroll<T> from(List<T> items, IntFunction<? extends ScrollPosition> positionFunction, boolean hasNext) {
|
||||
return new ScrollImpl<>(items, positionFunction, hasNext);
|
||||
static <T> Window<T> from(List<T> items, IntFunction<? extends ScrollPosition> positionFunction, boolean hasNext) {
|
||||
return new WindowImpl<>(items, positionFunction, hasNext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of elements in this scroll.
|
||||
* Returns the number of elements in this window.
|
||||
*
|
||||
* @return the number of elements in this scroll.
|
||||
* @return the number of elements in this window.
|
||||
*/
|
||||
int size();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this scroll contains no elements.
|
||||
* Returns {@code true} if this window contains no elements.
|
||||
*
|
||||
* @return {@code true} if this scroll contains no elements
|
||||
* @return {@code true} if this window contains no elements
|
||||
*/
|
||||
boolean isEmpty();
|
||||
|
||||
/**
|
||||
* Returns the scroll content as {@link List}.
|
||||
* Returns the windows content as {@link List}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
List<T> getContent();
|
||||
|
||||
/**
|
||||
* Returns whether the current scroll is the last one.
|
||||
* Returns whether the current window is the last one.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@@ -90,9 +91,9 @@ public interface Scroll<T> extends Streamable<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if there is a next scroll.
|
||||
* Returns if there is a next window.
|
||||
*
|
||||
* @return if there is a next scroll window.
|
||||
* @return if there is a next window window.
|
||||
*/
|
||||
boolean hasNext();
|
||||
|
||||
@@ -123,45 +124,12 @@ public interface Scroll<T> extends Streamable<T> {
|
||||
return positionAt(index);
|
||||
}
|
||||
|
||||
// TODO: First and last seem to conflict with first/last scroll or first/last position of elements.
|
||||
// these methods should rather express the position of the first element within this scroll and the scroll position
|
||||
// to be used to get the next Scroll.
|
||||
/**
|
||||
* Returns the first {@link ScrollPosition} or throw {@link NoSuchElementException} if the list is empty.
|
||||
*
|
||||
* @return the first {@link ScrollPosition}.
|
||||
* @throws NoSuchElementException if this result is empty.
|
||||
*/
|
||||
default ScrollPosition firstPosition() {
|
||||
|
||||
if (size() == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return positionAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last {@link ScrollPosition} or throw {@link NoSuchElementException} if the list is empty.
|
||||
*
|
||||
* @return the last {@link ScrollPosition}.
|
||||
* @throws NoSuchElementException if this result is empty.
|
||||
*/
|
||||
default ScrollPosition lastPosition() {
|
||||
|
||||
if (size() == 0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
return positionAt(size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Scroll} with the content of the current one mapped by the given {@code converter}.
|
||||
* Returns a new {@link Window} with the content of the current one mapped by the given {@code converter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
* @return a new {@link Scroll} with the content of the current one mapped by the given {@code converter}.
|
||||
* @return a new {@link Window} with the content of the current one mapped by the given {@code converter}.
|
||||
*/
|
||||
<U> Scroll<U> map(Function<? super T, ? extends U> converter);
|
||||
<U> Window<U> map(Function<? super T, ? extends U> converter);
|
||||
|
||||
}
|
||||
@@ -26,19 +26,19 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Default {@link Scroll} implementation.
|
||||
* Default {@link Window} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.1
|
||||
*/
|
||||
class ScrollImpl<T> implements Scroll<T> {
|
||||
class WindowImpl<T> implements Window<T> {
|
||||
|
||||
private final List<T> items;
|
||||
private final IntFunction<? extends ScrollPosition> positionFunction;
|
||||
|
||||
private final boolean hasNext;
|
||||
|
||||
ScrollImpl(List<T> items, IntFunction<? extends ScrollPosition> positionFunction, boolean hasNext) {
|
||||
WindowImpl(List<T> items, IntFunction<? extends ScrollPosition> positionFunction, boolean hasNext) {
|
||||
|
||||
Assert.notNull(items, "List of items must not be null");
|
||||
Assert.notNull(positionFunction, "Position function must not be null");
|
||||
@@ -79,11 +79,11 @@ class ScrollImpl<T> implements Scroll<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <U> Scroll<U> map(Function<? super T, ? extends U> converter) {
|
||||
public <U> Window<U> map(Function<? super T, ? extends U> converter) {
|
||||
|
||||
Assert.notNull(converter, "Function must not be null");
|
||||
|
||||
return new ScrollImpl<>(stream().map(converter).collect(Collectors.toList()), positionFunction, hasNext);
|
||||
return new WindowImpl<>(stream().map(converter).collect(Collectors.toList()), positionFunction, hasNext);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -98,7 +98,7 @@ class ScrollImpl<T> implements Scroll<T> {
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ScrollImpl<?> that = (ScrollImpl<?>) o;
|
||||
WindowImpl<?> that = (WindowImpl<?>) o;
|
||||
return ObjectUtils.nullSafeEquals(items, that.items)
|
||||
&& ObjectUtils.nullSafeEquals(positionFunction, that.positionFunction)
|
||||
&& ObjectUtils.nullSafeEquals(hasNext, that.hasNext);
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link Iterator} over multiple {@link Window Windows} obtained via a {@link Function window function}, that keeps track of
|
||||
* the current {@link ScrollPosition} returning the Window {@link Window#getContent() content} on {@link #next()}.
|
||||
* <pre class="code">
|
||||
* WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10By...("spring", position))
|
||||
* .startingAt(OffsetScrollPosition.initial());
|
||||
* while (users.hasNext()) {
|
||||
* users.next().forEach(user -> {
|
||||
* // consume the user
|
||||
* });
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 3.1
|
||||
*/
|
||||
public class WindowIterator<T> implements Iterator<List<T>> {
|
||||
|
||||
private final Function<ScrollPosition, Window<T>> windowFunction;
|
||||
private ScrollPosition currentPosition;
|
||||
|
||||
@Nullable //
|
||||
private Window<T> currentWindow;
|
||||
|
||||
/**
|
||||
* Entrypoint to create a new {@link WindowIterator} for the given windowFunction.
|
||||
*
|
||||
* @param windowFunction must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return new instance of {@link WindowIteratorBuilder}.
|
||||
*/
|
||||
public static <T> WindowIteratorBuilder<T> of(Function<ScrollPosition, Window<T>> windowFunction) {
|
||||
return new WindowIteratorBuilder(windowFunction);
|
||||
}
|
||||
|
||||
WindowIterator(Function<ScrollPosition, Window<T>> windowFunction, ScrollPosition position) {
|
||||
|
||||
this.windowFunction = windowFunction;
|
||||
this.currentPosition = position;
|
||||
this.currentWindow = doScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentWindow != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> next() {
|
||||
|
||||
List<T> toReturn = new ArrayList<>(currentWindow.getContent());
|
||||
currentPosition = currentWindow.positionAt(currentWindow.size() -1);
|
||||
currentWindow = doScroll();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Window<T> doScroll() {
|
||||
|
||||
if (currentWindow != null && !currentWindow.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Window<T> window = windowFunction.apply(currentPosition);
|
||||
if (window.isEmpty() && window.isLast()) {
|
||||
return null;
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder API to construct a {@link WindowIterator}.
|
||||
*
|
||||
* @param <T>
|
||||
* @author Christoph Strobl
|
||||
* @since 3.1
|
||||
*/
|
||||
public static class WindowIteratorBuilder<T> {
|
||||
|
||||
private Function<ScrollPosition, Window<T>> windowFunction;
|
||||
|
||||
WindowIteratorBuilder(Function<ScrollPosition, Window<T>> windowFunction) {
|
||||
this.windowFunction = windowFunction;
|
||||
}
|
||||
|
||||
public WindowIterator<T> startingAt(ScrollPosition position) {
|
||||
|
||||
Assert.state(windowFunction != null, "WindowFunction cannot not be null");
|
||||
return new WindowIterator<>(windowFunction, position);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import org.springframework.data.domain.Window;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -26,7 +27,6 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Scroll;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -165,7 +165,7 @@ public interface FluentQuery<T> {
|
||||
List<T> all();
|
||||
|
||||
/**
|
||||
* Get all matching elements as {@link Scroll} to start result scrolling or resume scrolling at
|
||||
* Get all matching elements as {@link Window} to start result scrolling or resume scrolling at
|
||||
* {@code scrollPosition}.
|
||||
*
|
||||
* @param scrollPosition must not be {@literal null}.
|
||||
@@ -174,7 +174,7 @@ public interface FluentQuery<T> {
|
||||
* @throws UnsupportedOperationException if not supported by the underlying implementation.
|
||||
* @since 3.1
|
||||
*/
|
||||
default Scroll<T> scroll(ScrollPosition scrollPosition) {
|
||||
default Window<T> scroll(ScrollPosition scrollPosition) {
|
||||
throw new UnsupportedOperationException("Scrolling not supported");
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ public interface FluentQuery<T> {
|
||||
Flux<T> all();
|
||||
|
||||
/**
|
||||
* Get all matching elements as {@link Scroll} to start result scrolling or resume scrolling at
|
||||
* Get all matching elements as {@link Window} to start result scrolling or resume scrolling at
|
||||
* {@code scrollPosition}.
|
||||
*
|
||||
* @param scrollPosition must not be {@literal null}.
|
||||
@@ -270,7 +270,7 @@ public interface FluentQuery<T> {
|
||||
* @throws UnsupportedOperationException if not supported by the underlying implementation.
|
||||
* @since 3.1
|
||||
*/
|
||||
default Mono<Scroll<T>> scroll(ScrollPosition scrollPosition) {
|
||||
default Mono<Window<T>> scroll(ScrollPosition scrollPosition) {
|
||||
throw new UnsupportedOperationException("Scrolling not supported");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Scroll;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.domain.ScrollPosition;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
@@ -97,7 +97,7 @@ public class QueryMethod {
|
||||
}
|
||||
|
||||
if (hasParameterOfType(method, ScrollPosition.class)) {
|
||||
assertReturnTypeAssignable(method, Collections.singleton(Scroll.class));
|
||||
assertReturnTypeAssignable(method, Collections.singleton(Window.class));
|
||||
}
|
||||
|
||||
Assert.notNull(this.parameters,
|
||||
@@ -203,13 +203,13 @@ public class QueryMethod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the query method will return a {@link Scroll}.
|
||||
* Returns whether the query method will return a {@link Window}.
|
||||
*
|
||||
* @return
|
||||
* @since 3.1
|
||||
*/
|
||||
public boolean isScrollQuery() {
|
||||
return org.springframework.util.ClassUtils.isAssignable(Scroll.class, unwrappedReturnType);
|
||||
return org.springframework.util.ClassUtils.isAssignable(Window.class, unwrappedReturnType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Scroll;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
@@ -144,8 +144,8 @@ public class ResultProcessor {
|
||||
|
||||
ChainingConverter converter = ChainingConverter.of(type.getReturnedType(), preparingConverter).and(this.converter);
|
||||
|
||||
if (source instanceof Scroll<?> && method.isScrollQuery()) {
|
||||
return (T) ((Scroll<?>) source).map(converter::convert);
|
||||
if (source instanceof Window<?> && method.isScrollQuery()) {
|
||||
return (T) ((Window<?>) source).map(converter::convert);
|
||||
}
|
||||
|
||||
if (source instanceof Slice && (method.isPageQuery() || method.isSliceQuery())) {
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Scroll;
|
||||
import org.springframework.data.domain.Window;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.geo.GeoResults;
|
||||
import org.springframework.data.util.CustomCollections;
|
||||
@@ -98,7 +98,7 @@ public abstract class QueryExecutionConverters {
|
||||
ALLOWED_PAGEABLE_TYPES.add(Slice.class);
|
||||
ALLOWED_PAGEABLE_TYPES.add(Page.class);
|
||||
ALLOWED_PAGEABLE_TYPES.add(List.class);
|
||||
ALLOWED_PAGEABLE_TYPES.add(Scroll.class);
|
||||
ALLOWED_PAGEABLE_TYPES.add(Window.class);
|
||||
|
||||
WRAPPER_TYPES.add(NullableWrapperToCompletableFutureConverter.getWrapperType());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user