Skip to content

Commit 2564f6a

Browse files
mp911dechristophstrobl
authored andcommitted
Polishing.
Refactor WindowIterator to return individual objects during scrolling. Original Pull Request: #2787
1 parent 0c0c1af commit 2564f6a

File tree

5 files changed

+149
-104
lines changed

5 files changed

+149
-104
lines changed

src/main/asciidoc/repositories-scrolling.adoc

+7-7
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,19 @@ do {
2020
2121
// obtain the next Scroll
2222
users = repository.findFirst10ByLastnameOrderByFirstname("Doe", users.positionAt(users.size() - 1));
23-
} while (!users.isEmpty() && !users.isLast());
23+
} while (!users.isEmpty() && users.hasNext());
2424
----
2525

26-
`WindowIterator` provides a specialized implementation that simplifies consumption of multiple `Window` instances by removing the need to check for the presence of a next `Window` and applying the `ScrollPosition`.
26+
`WindowIterator` provides a utility to simplify scrolling across ``Window``s by removing the need to check for the presence of a next `Window` and applying the `ScrollPosition`.
2727

2828
[source,java]
2929
----
3030
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
3131
.startingAt(OffsetScrollPosition.initial());
3232
3333
while (users.hasNext()) {
34-
users.next().forEach(user -> {
35-
// consume the user
36-
});
34+
User u = users.next();
35+
// consume the user
3736
}
3837
----
3938

@@ -56,7 +55,8 @@ interface UserRepository extends Repository<User, Long> {
5655
WindowIterator<User> users = WindowIterator.of(position -> repository.findFirst10ByLastnameOrderByFirstname("Doe", position))
5756
.startingAt(OffsetScrollPosition.initial()); <1>
5857
----
59-
<1> Start from the initial offset at position 0;
58+
59+
<1> Start from the initial offset at position `0`.
6060
====
6161

6262
[[repositories.scrolling.keyset]]
@@ -76,7 +76,7 @@ The database needs only constructing a much smaller result from the given keyset
7676

7777
[WARNING]
7878
====
79-
Keyset-Filtering requires properties used for sorting to be non nullable.
79+
Keyset-Filtering requires the keyset properties (those used for sorting) to be non-nullable.
8080
This limitation applies due to the store specific `null` value handling of comparison operators as well as the need to run queries against an indexed source.
8181
Keyset-Filtering on nullable properties will lead to unexpected results.
8282
====

src/main/java/org/springframework/data/domain/KeysetScrollPosition.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ public String toString() {
129129
/**
130130
* Keyset scrolling direction.
131131
*/
132-
enum Direction {
132+
public enum Direction {
133133

134134
/**
135135
* Forward (default) direction to scroll from the beginning of the results to their end.

src/main/java/org/springframework/data/domain/Window.java

+22-3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424

2525
/**
2626
* A set of data consumed from an underlying query result. A {@link Window} is similar to {@link Slice} in the sense
27-
* that it contains a subset of the actual query results for easier consumption of large result sets. The window is less
28-
* opinionated about the actual data retrieval, whether the query has used index/offset, keyset-based pagination or
27+
* that it contains a subset of the actual query results for easier scrolling across large result sets. The window is
28+
* less opinionated about the actual data retrieval, whether the query has used index/offset, keyset-based pagination or
2929
* cursor resume tokens.
3030
*
3131
* @author Mark Paluch
@@ -93,16 +93,33 @@ default boolean isLast() {
9393
/**
9494
* Returns if there is a next window.
9595
*
96-
* @return if there is a next window window.
96+
* @return if there is a next window.
9797
*/
9898
boolean hasNext();
9999

100+
/**
101+
* Returns whether the underlying scroll mechanism can provide a {@link ScrollPosition} at {@code index}.
102+
*
103+
* @param index
104+
* @return {@code true} if a {@link ScrollPosition} can be created; {@code false} otherwise.
105+
* @see #positionAt(int)
106+
*/
107+
default boolean hasPosition(int index) {
108+
try {
109+
return positionAt(index) != null;
110+
} catch (IllegalStateException e) {
111+
return false;
112+
}
113+
}
114+
100115
/**
101116
* Returns the {@link ScrollPosition} at {@code index}.
102117
*
103118
* @param index
104119
* @return
105120
* @throws IndexOutOfBoundsException if the index is out of range ({@code index < 0 || index >= size()}).
121+
* @throws IllegalStateException if the underlying scroll mechanism cannot provide a scroll position for the given
122+
* object.
106123
*/
107124
ScrollPosition positionAt(int index);
108125

@@ -112,6 +129,8 @@ default boolean isLast() {
112129
* @param object
113130
* @return
114131
* @throws NoSuchElementException if the object is not part of the result.
132+
* @throws IllegalStateException if the underlying scroll mechanism cannot provide a scroll position for the given
133+
* object.
115134
*/
116135
default ScrollPosition positionAt(T object) {
117136

src/main/java/org/springframework/data/domain/WindowIterator.java

+60-32
Original file line numberDiff line numberDiff line change
@@ -15,37 +15,40 @@
1515
*/
1616
package org.springframework.data.domain;
1717

18-
import java.util.ArrayList;
1918
import java.util.Iterator;
20-
import java.util.List;
19+
import java.util.NoSuchElementException;
2120
import java.util.function.Function;
2221

2322
import org.springframework.lang.Nullable;
2423
import org.springframework.util.Assert;
2524

2625
/**
27-
* An {@link Iterator} over multiple {@link Window Windows} obtained via a {@link Function window function}, that keeps track of
28-
* the current {@link ScrollPosition} returning the Window {@link Window#getContent() content} on {@link #next()}.
26+
* An {@link Iterator} over multiple {@link Window Windows} obtained via a {@link Function window function}, that keeps
27+
* track of the current {@link ScrollPosition} allowing scrolling across all result elements.
28+
*
2929
* <pre class="code">
30-
* WindowIterator&lt;User&gt; users = WindowIterator.of(position -> repository.findFirst10By...("spring", position))
30+
* WindowIterator&lt;User&gt; users = WindowIterator.of(position -> repository.findFirst10By("spring", position))
3131
* .startingAt(OffsetScrollPosition.initial());
32+
*
3233
* while (users.hasNext()) {
33-
* users.next().forEach(user -> {
34-
* // consume the user
35-
* });
34+
* User u = users.next();
35+
* // consume user
3636
* }
3737
* </pre>
3838
*
3939
* @author Christoph Strobl
40+
* @author Mark Paluch
4041
* @since 3.1
4142
*/
42-
public class WindowIterator<T> implements Iterator<List<T>> {
43+
public class WindowIterator<T> implements Iterator<T> {
4344

4445
private final Function<ScrollPosition, Window<T>> windowFunction;
46+
4547
private ScrollPosition currentPosition;
4648

47-
@Nullable //
48-
private Window<T> currentWindow;
49+
private @Nullable Window<T> currentWindow;
50+
51+
private @Nullable Iterator<T> currentIterator;
4952

5053
/**
5154
* Entrypoint to create a new {@link WindowIterator} for the given windowFunction.
@@ -55,42 +58,57 @@ public class WindowIterator<T> implements Iterator<List<T>> {
5558
* @return new instance of {@link WindowIteratorBuilder}.
5659
*/
5760
public static <T> WindowIteratorBuilder<T> of(Function<ScrollPosition, Window<T>> windowFunction) {
58-
return new WindowIteratorBuilder(windowFunction);
61+
return new WindowIteratorBuilder<>(windowFunction);
5962
}
6063

6164
WindowIterator(Function<ScrollPosition, Window<T>> windowFunction, ScrollPosition position) {
6265

6366
this.windowFunction = windowFunction;
6467
this.currentPosition = position;
65-
this.currentWindow = doScroll();
6668
}
6769

6870
@Override
6971
public boolean hasNext() {
70-
return currentWindow != null;
71-
}
7272

73-
@Override
74-
public List<T> next() {
73+
// use while loop instead of recursion to fetch the next window.
74+
do {
75+
if (currentWindow == null) {
76+
currentWindow = windowFunction.apply(currentPosition);
77+
}
78+
79+
if (currentIterator == null) {
80+
if (currentWindow != null) {
81+
currentIterator = currentWindow.iterator();
82+
}
83+
}
84+
85+
if (currentIterator != null) {
7586

76-
List<T> toReturn = new ArrayList<>(currentWindow.getContent());
77-
currentPosition = currentWindow.positionAt(currentWindow.size() -1);
78-
currentWindow = doScroll();
79-
return toReturn;
87+
if (currentIterator.hasNext()) {
88+
return true;
89+
}
90+
91+
if (currentWindow != null && currentWindow.hasNext()) {
92+
93+
currentPosition = currentWindow.positionAt(currentWindow.size() - 1);
94+
currentIterator = null;
95+
currentWindow = null;
96+
continue;
97+
}
98+
}
99+
100+
return false;
101+
} while (true);
80102
}
81103

82-
@Nullable
83-
Window<T> doScroll() {
104+
@Override
105+
public T next() {
84106

85-
if (currentWindow != null && !currentWindow.hasNext()) {
86-
return null;
107+
if (!hasNext()) {
108+
throw new NoSuchElementException();
87109
}
88110

89-
Window<T> window = windowFunction.apply(currentPosition);
90-
if (window.isEmpty() && window.isLast()) {
91-
return null;
92-
}
93-
return window;
111+
return currentIterator.next();
94112
}
95113

96114
/**
@@ -102,15 +120,25 @@ Window<T> doScroll() {
102120
*/
103121
public static class WindowIteratorBuilder<T> {
104122

105-
private Function<ScrollPosition, Window<T>> windowFunction;
123+
private final Function<ScrollPosition, Window<T>> windowFunction;
106124

107125
WindowIteratorBuilder(Function<ScrollPosition, Window<T>> windowFunction) {
126+
127+
Assert.notNull(windowFunction, "WindowFunction must not be null");
128+
108129
this.windowFunction = windowFunction;
109130
}
110131

132+
/**
133+
* Create a {@link WindowIterator} given {@link ScrollPosition}.
134+
*
135+
* @param position
136+
* @return
137+
*/
111138
public WindowIterator<T> startingAt(ScrollPosition position) {
112139

113-
Assert.state(windowFunction != null, "WindowFunction cannot not be null");
140+
Assert.notNull(position, "ScrollPosition must not be null");
141+
114142
return new WindowIterator<>(windowFunction, position);
115143
}
116144
}

0 commit comments

Comments
 (0)