Skip to content

Commit 15bb8aa

Browse files
committed
Polishing.
Refine documentation. Introduce isLimited method to mirror isPresent/isSorted semantics. Introduce Pageable.toLimit() method to deduplicate code. See #2827 Original pull request: #2836
1 parent a5408a4 commit 15bb8aa

File tree

8 files changed

+118
-94
lines changed

8 files changed

+118
-94
lines changed

src/main/asciidoc/repositories-paging-sorting.adoc

+4-4
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,23 @@ By default, this query is derived from the query you actually trigger.
6464
[IMPORTANT]
6565
====
6666
Special parameters may only be used once within a query method. +
67-
Some of the special parameters described above are mutually exclusive.
67+
Some special parameters described above are mutually exclusive.
6868
Please consider the following list of invalid parameter combinations.
6969
7070
|===
7171
| Parameters | Example | Reason
7272
73-
| `Pageable` & `Sort`
73+
| `Pageable` and `Sort`
7474
| `findBy...(Pageable page, Sort sort)`
7575
| `Pageable` already defines `Sort`
7676
77-
| `Pageable` & `Limit`
77+
| `Pageable` and `Limit`
7878
| `findBy...(Pageable page, Limit limit)`
7979
| `Pageable` already defines a limit.
8080
8181
|===
8282
83-
The `Top` keyword used to limit results can be used to along with `Pageable` whereas `Top` defines the total maximum of results, whereas the Pageable parameter may reduce this this number.
83+
The `Top` keyword used to limit results can be used to along with `Pageable` whereas `Top` defines the total maximum of results, whereas the Pageable parameter may reduce this number.
8484
====
8585

8686
[[repositories.scrolling.guidance]]

src/main/asciidoc/repository-query-return-types-reference.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Some store modules may define their own result wrapper types.
2525
|`Optional<T>`|A Java 8 or Guava `Optional`. Expects the query method to return one result at most. If no result is found, `Optional.empty()` or `Optional.absent()` is returned. More than one result triggers an `IncorrectResultSizeDataAccessException`.
2626
|`Option<T>`|Either a Scala or Vavr `Option` type. Semantically the same behavior as Java 8's `Optional`, described earlier.
2727
|`Stream<T>`|A Java 8 `Stream`.
28-
|`Streamable<T>`|A convenience extension of `Iterable` that directy exposes methods to stream, map and filter results, concatenate them etc.
28+
|`Streamable<T>`|A convenience extension of `Iterable` that directly exposes methods to stream, map and filter results, concatenate them etc.
2929
|Types that implement `Streamable` and take a `Streamable` constructor or factory method argument|Types that expose a constructor or `….of(…)`/`….valueOf(…)` factory method taking a `Streamable` as argument. See <<repositories.collections-and-iterables.streamable-wrapper>> for details.
3030
|Vavr `Seq`, `List`, `Map`, `Set`|Vavr collection types. See <<repositories.collections-and-iterables.vavr>> for details.
3131
|`Future<T>`|A `Future`. Expects a method to be annotated with `@Async` and requires Spring's asynchronous method execution capability to be enabled.

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

+30-20
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,12 @@
2828
* over using {@literal null} or {@link java.util.Optional#empty()} to indicate the absence of an actual {@link Limit}.
2929
* </p>
3030
* {@link Limit} itself does not make assumptions about the actual {@link #max()} value sign. This means that a negative
31-
* value may be valid within a defined context. Implementations should override {@link #isUnlimited()} to fit their
32-
* needs and enforce restrictions if needed.
33-
*
31+
* value may be valid within a defined context.
32+
*
3433
* @author Christoph Strobl
3534
* @since 3.2
3635
*/
37-
public sealed interface Limit permits Limited, Unlimited {
38-
39-
/**
40-
* @return the max number of potential results.
41-
*/
42-
int max();
43-
44-
/**
45-
* @return {@literal true} if no limiting (maximum value) should be applied.
46-
*/
47-
default boolean isUnlimited() {
48-
return Unlimited.INSTANCE.equals(this);
49-
}
36+
public sealed interface Limit permits Limited,Unlimited {
5037

5138
/**
5239
* @return a {@link Limit} instance that does not define {@link #max()} and answers {@link #isUnlimited()} with
@@ -66,6 +53,23 @@ static Limit of(int max) {
6653
return new Limited(max);
6754
}
6855

56+
/**
57+
* @return the max number of potential results.
58+
*/
59+
int max();
60+
61+
/**
62+
* @return {@literal true} if limiting (maximum value) should be applied.
63+
*/
64+
boolean isLimited();
65+
66+
/**
67+
* @return {@literal true} if no limiting (maximum value) should be applied.
68+
*/
69+
default boolean isUnlimited() {
70+
return !isLimited();
71+
}
72+
6973
final class Limited implements Limit {
7074

7175
private final int max;
@@ -79,6 +83,11 @@ public int max() {
7983
return max;
8084
}
8185

86+
@Override
87+
public boolean isLimited() {
88+
return true;
89+
}
90+
8291
@Override
8392
public boolean equals(Object obj) {
8493

@@ -97,7 +106,7 @@ public boolean equals(Object obj) {
97106

98107
@Override
99108
public int hashCode() {
100-
return (int) (max ^ (max >>> 32));
109+
return max ^ (max >>> 32);
101110
}
102111
}
103112

@@ -110,12 +119,13 @@ final class Unlimited implements Limit {
110119
@Override
111120
public int max() {
112121
throw new IllegalStateException(
113-
"Unlimited does not define 'max'. Please check 'isUnlimited' before attempting to read 'max'");
122+
"Unlimited does not define 'max'. Please check 'isLimited' before attempting to read 'max'");
114123
}
115124

116125
@Override
117-
public boolean isUnlimited() {
118-
return true;
126+
public boolean isLimited() {
127+
return false;
119128
}
129+
120130
}
121131
}

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

+16
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,22 @@ default Optional<Pageable> toOptional() {
173173
return isUnpaged() ? Optional.empty() : Optional.of(this);
174174
}
175175

176+
/**
177+
* Returns an {@link Limit} from this pageable if the page request {@link #isPaged() is paged} or
178+
* {@link Limit#unlimited()} otherwise.
179+
*
180+
* @return
181+
* @since 3.2
182+
*/
183+
default Limit toLimit() {
184+
185+
if (isUnpaged()) {
186+
return Limit.unlimited();
187+
}
188+
189+
return Limit.of(getPageSize());
190+
}
191+
176192
/**
177193
* Returns an {@link OffsetScrollPosition} from this pageable if the page request {@link #isPaged() is paged}.
178194
*

src/main/java/org/springframework/data/repository/query/ParameterAccessor.java

+2-6
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,11 @@ public interface ParameterAccessor extends Iterable<Object> {
6060
* assignable to {@link Limit} can be found {@link Limit} will be created out of {@link Pageable#getPageSize()} if
6161
* present.
6262
*
63-
* @return {@link Limit#unlimited()} by default.
63+
* @return
6464
* @since 3.2
6565
*/
6666
default Limit getLimit() {
67-
68-
if (getPageable().isUnpaged()) {
69-
return Limit.unlimited();
70-
}
71-
return Limit.of(getPageable().getPageSize());
67+
return getPageable().toLimit();
7268
}
7369

7470
/**

src/main/java/org/springframework/data/repository/query/ParametersParameterAccessor.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public Limit getLimit() {
157157
if (parameters.hasPageableParameter()) {
158158

159159
Pageable pageable = (Pageable) values[parameters.getPageableIndex()];
160-
return pageable.isPaged() ? Limit.of(pageable.getPageSize()) : Limit.unlimited();
160+
return pageable.toLimit();
161161
}
162162

163163
return Limit.unlimited();

src/main/java/org/springframework/data/repository/query/QueryMethod.java

+62-62
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,68 @@ public QueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory
103103
validate();
104104
}
105105

106+
private void validate() {
107+
108+
QueryMethodValidator.validate(method);
109+
110+
if (hasParameterOfType(method, Pageable.class)) {
111+
112+
if (!isStreamQuery()) {
113+
assertReturnTypeAssignable(method, QueryExecutionConverters.getAllowedPageableTypes());
114+
}
115+
116+
if (hasParameterOfType(method, Sort.class)) {
117+
throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameters. "
118+
+ "Use sorting capabilities on Pageable instead; Offending method: %s", method));
119+
}
120+
}
121+
122+
if (hasParameterOfType(method, ScrollPosition.class)) {
123+
assertReturnTypeAssignable(method, Collections.singleton(Window.class));
124+
}
125+
126+
Assert.notNull(this.parameters,
127+
() -> String.format("Parameters extracted from method '%s' must not be null", method.getName()));
128+
129+
if (isPageQuery()) {
130+
Assert.isTrue(this.parameters.hasPageableParameter(),
131+
String.format("Paging query needs to have a Pageable parameter; Offending method: %s", method));
132+
}
133+
134+
if (isScrollQuery()) {
135+
136+
Assert.isTrue(this.parameters.hasScrollPositionParameter() || this.parameters.hasPageableParameter(),
137+
String.format("Scroll query needs to have a ScrollPosition parameter; Offending method: %s", method));
138+
}
139+
}
140+
141+
private boolean calculateIsCollectionQuery() {
142+
143+
if (isPageQuery() || isSliceQuery() || isScrollQuery()) {
144+
return false;
145+
}
146+
147+
TypeInformation<?> returnTypeInformation = metadata.getReturnType(method);
148+
149+
// Check against simple wrapper types first
150+
if (metadata.getDomainTypeInformation()
151+
.isAssignableFrom(NullableWrapperConverters.unwrapActualType(returnTypeInformation))) {
152+
return false;
153+
}
154+
155+
Class<?> returnType = returnTypeInformation.getType();
156+
157+
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
158+
return true;
159+
}
160+
161+
if (QueryExecutionConverters.supports(unwrappedReturnType)) {
162+
return !QueryExecutionConverters.isSingleValue(unwrappedReturnType);
163+
}
164+
165+
return TypeInformation.of(unwrappedReturnType).isCollectionLike();
166+
}
167+
106168
/**
107169
* Creates a {@link Parameters} instance.
108170
*
@@ -265,68 +327,6 @@ public String toString() {
265327
return method.toString();
266328
}
267329

268-
public void validate() {
269-
270-
QueryMethodValidator.validate(method);
271-
272-
if (hasParameterOfType(method, Pageable.class)) {
273-
274-
if (!isStreamQuery()) {
275-
assertReturnTypeAssignable(method, QueryExecutionConverters.getAllowedPageableTypes());
276-
}
277-
278-
if (hasParameterOfType(method, Sort.class)) {
279-
throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameters. "
280-
+ "Use sorting capabilities on Pageable instead; Offending method: %s", method));
281-
}
282-
}
283-
284-
if (hasParameterOfType(method, ScrollPosition.class)) {
285-
assertReturnTypeAssignable(method, Collections.singleton(Window.class));
286-
}
287-
288-
Assert.notNull(this.parameters,
289-
() -> String.format("Parameters extracted from method '%s' must not be null", method.getName()));
290-
291-
if (isPageQuery()) {
292-
Assert.isTrue(this.parameters.hasPageableParameter(),
293-
String.format("Paging query needs to have a Pageable parameter; Offending method: %s", method));
294-
}
295-
296-
if (isScrollQuery()) {
297-
298-
Assert.isTrue(this.parameters.hasScrollPositionParameter() || this.parameters.hasPageableParameter(),
299-
String.format("Scroll query needs to have a ScrollPosition parameter; Offending method: %s", method));
300-
}
301-
}
302-
303-
private boolean calculateIsCollectionQuery() {
304-
305-
if (isPageQuery() || isSliceQuery() || isScrollQuery()) {
306-
return false;
307-
}
308-
309-
TypeInformation<?> returnTypeInformation = metadata.getReturnType(method);
310-
311-
// Check against simple wrapper types first
312-
if (metadata.getDomainTypeInformation()
313-
.isAssignableFrom(NullableWrapperConverters.unwrapActualType(returnTypeInformation))) {
314-
return false;
315-
}
316-
317-
Class<?> returnType = returnTypeInformation.getType();
318-
319-
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
320-
return true;
321-
}
322-
323-
if (QueryExecutionConverters.supports(unwrappedReturnType)) {
324-
return !QueryExecutionConverters.isSingleValue(unwrappedReturnType);
325-
}
326-
327-
return TypeInformation.of(unwrappedReturnType).isCollectionLike();
328-
}
329-
330330
private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
331331

332332
TypeInformation<?> returnType = metadata.getReturnType(method);

src/test/java/org/springframework/data/domain/LimitUnitTests.java

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import org.junit.jupiter.params.provider.ValueSource;
2323

2424
/**
25+
* Unit tests for {@link Limit}.
26+
*
2527
* @author Christoph Strobl
2628
* @soundtrack Rise Against - Tragedy + Time
2729
*/

0 commit comments

Comments
 (0)