Skip to content

Commit a5408a4

Browse files
christophstroblmp911de
authored andcommitted
Introduce Limit type to limit repository query results.
We now accept Limit as type to express dynamic repository query limits. Closes #2827 Original pull request: #2836
1 parent 0d9a911 commit a5408a4

16 files changed

+604
-47
lines changed

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

+32-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
[[repositories.special-parameters]]
2-
=== Paging, Iterating Large Results, Sorting
2+
=== Paging, Iterating Large Results, Sorting & Limiting
33

44
To handle parameters in your query, define method parameters as already seen in the preceding examples.
5-
Besides that, the infrastructure recognizes certain specific types like `Pageable` and `Sort`, to apply pagination and sorting to your queries dynamically.
5+
Besides that, the infrastructure recognizes certain specific types like `Pageable`, `Sort` and `Limit`, to apply pagination, sorting and limiting to your queries dynamically.
66
The following example demonstrates these features:
77

88
ifdef::feature-scroll[]
9-
.Using `Pageable`, `Slice`, `ScrollPosition`, and `Sort` in query methods
9+
.Using `Pageable`, `Slice`, `ScrollPosition`, `Sort` and `Limit` in query methods
1010
====
1111
[source,java]
1212
----
@@ -18,13 +18,15 @@ Window<User> findTop10ByLastname(String lastname, ScrollPosition position, Sort
1818
1919
List<User> findByLastname(String lastname, Sort sort);
2020
21+
List<User> findByLastname(String lastname, Sort sort, Limit limit);
22+
2123
List<User> findByLastname(String lastname, Pageable pageable);
2224
----
2325
====
2426
endif::[]
2527

2628
ifndef::feature-scroll[]
27-
.Using `Pageable`, `Slice`, and `Sort` in query methods
29+
.Using `Pageable`, `Slice`, `Sort` and `Limit` in query methods
2830
====
2931
[source,java]
3032
----
@@ -34,13 +36,15 @@ Slice<User> findByLastname(String lastname, Pageable pageable);
3436
3537
List<User> findByLastname(String lastname, Sort sort);
3638
39+
List<User> findByLastname(String lastname, Sort sort, Limit limit);
40+
3741
List<User> findByLastname(String lastname, Pageable pageable);
3842
----
3943
====
4044
endif::[]
4145

42-
IMPORTANT: APIs taking `Sort` and `Pageable` expect non-`null` values to be handed into methods.
43-
If you do not want to apply any sorting or pagination, use `Sort.unsorted()` and `Pageable.unpaged()`.
46+
IMPORTANT: APIs taking `Sort`, `Pageable` and `Limit` expect non-`null` values to be handed into methods.
47+
If you do not want to apply any sorting or pagination, use `Sort.unsorted()`, `Pageable.unpaged()` and `Limit.unlimited()`.
4448

4549
The first method lets you pass an `org.springframework.data.domain.Pageable` instance to the query method to dynamically add paging to your statically defined query.
4650
A `Page` knows about the total number of elements and pages available.
@@ -57,6 +61,28 @@ Rather, it restricts the query to look up only the given range of entities.
5761
NOTE: To find out how many pages you get for an entire query, you have to trigger an additional count query.
5862
By default, this query is derived from the query you actually trigger.
5963

64+
[IMPORTANT]
65+
====
66+
Special parameters may only be used once within a query method. +
67+
Some of the special parameters described above are mutually exclusive.
68+
Please consider the following list of invalid parameter combinations.
69+
70+
|===
71+
| Parameters | Example | Reason
72+
73+
| `Pageable` & `Sort`
74+
| `findBy...(Pageable page, Sort sort)`
75+
| `Pageable` already defines `Sort`
76+
77+
| `Pageable` & `Limit`
78+
| `findBy...(Pageable page, Limit limit)`
79+
| `Pageable` already defines a limit.
80+
81+
|===
82+
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.
84+
====
85+
6086
[[repositories.scrolling.guidance]]
6187
==== Which Method is Appropriate?
6288

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.domain;
17+
18+
import org.springframework.data.domain.Limit.Limited;
19+
import org.springframework.data.domain.Limit.Unlimited;
20+
import org.springframework.util.ClassUtils;
21+
22+
/**
23+
* {@link Limit} represents the maximum value up to which an operation should continue processing. It may be used for
24+
* defining the {@link #max() maximum} number of results within a repository finder method or if applicable a template
25+
* operation.
26+
* <p>
27+
* A {@link Limit#isUnlimited()} is used to indicate that there is no {@link Limit} defined, which should be favoured
28+
* over using {@literal null} or {@link java.util.Optional#empty()} to indicate the absence of an actual {@link Limit}.
29+
* </p>
30+
* {@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+
*
34+
* @author Christoph Strobl
35+
* @since 3.2
36+
*/
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+
}
50+
51+
/**
52+
* @return a {@link Limit} instance that does not define {@link #max()} and answers {@link #isUnlimited()} with
53+
* {@literal true}.
54+
*/
55+
static Limit unlimited() {
56+
return Unlimited.INSTANCE;
57+
}
58+
59+
/**
60+
* Create a new {@link Limit} from the given {@literal max} value.
61+
*
62+
* @param max the maximum value.
63+
* @return new instance of {@link Limit}.
64+
*/
65+
static Limit of(int max) {
66+
return new Limited(max);
67+
}
68+
69+
final class Limited implements Limit {
70+
71+
private final int max;
72+
73+
Limited(int max) {
74+
this.max = max;
75+
}
76+
77+
@Override
78+
public int max() {
79+
return max;
80+
}
81+
82+
@Override
83+
public boolean equals(Object obj) {
84+
85+
if (obj == null) {
86+
return false;
87+
}
88+
if (!ClassUtils.isAssignable(Limit.class, obj.getClass())) {
89+
return false;
90+
}
91+
Limit that = (Limit) obj;
92+
if (this.isUnlimited() && that.isUnlimited()) {
93+
return true;
94+
}
95+
return max() == that.max();
96+
}
97+
98+
@Override
99+
public int hashCode() {
100+
return (int) (max ^ (max >>> 32));
101+
}
102+
}
103+
104+
final class Unlimited implements Limit {
105+
106+
static final Limit INSTANCE = new Unlimited();
107+
108+
Unlimited() {}
109+
110+
@Override
111+
public int max() {
112+
throw new IllegalStateException(
113+
"Unlimited does not define 'max'. Please check 'isUnlimited' before attempting to read 'max'");
114+
}
115+
116+
@Override
117+
public boolean isUnlimited() {
118+
return true;
119+
}
120+
}
121+
}

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*
2525
* @author Oliver Gierke
2626
* @author Mark Paluch
27+
* @author Christoph Strobl
2728
*/
2829
public interface Pageable {
2930

@@ -33,7 +34,18 @@ public interface Pageable {
3334
* @return
3435
*/
3536
static Pageable unpaged() {
36-
return Unpaged.INSTANCE;
37+
return unpaged(Sort.unsorted());
38+
}
39+
40+
/**
41+
* Returns a {@link Pageable} instance representing no pagination setup having a defined result {@link Sort order}.
42+
*
43+
* @param sort must not be {@literal null}, use {@link Sort#unsorted()} if needed.
44+
* @return never {@literal null}.
45+
* @since 3.2
46+
*/
47+
static Pageable unpaged(Sort sort) {
48+
return Unpaged.sorted(sort);
3749
}
3850

3951
/**

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

+14-4
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,21 @@
1919
* {@link Pageable} implementation to represent the absence of pagination information.
2020
*
2121
* @author Oliver Gierke
22+
* @author Christoph Strobl
2223
*/
23-
enum Unpaged implements Pageable {
24+
final class Unpaged implements Pageable {
2425

25-
INSTANCE;
26+
private static final Pageable UNSORTED = new Unpaged(Sort.unsorted());
27+
28+
private final Sort sort;
29+
30+
Unpaged(Sort sort) {
31+
this.sort = sort;
32+
}
33+
34+
static Pageable sorted(Sort sort) {
35+
return sort.isSorted() ? new Unpaged(sort) : UNSORTED;
36+
}
2637

2738
@Override
2839
public boolean isPaged() {
@@ -46,7 +57,7 @@ public boolean hasPrevious() {
4657

4758
@Override
4859
public Sort getSort() {
49-
return Sort.unsorted();
60+
return sort;
5061
}
5162

5263
@Override
@@ -78,5 +89,4 @@ public Pageable withPage(int pageNumber) {
7889

7990
throw new UnsupportedOperationException();
8091
}
81-
8292
}

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.springframework.core.MethodParameter;
2727
import org.springframework.core.ResolvableType;
28+
import org.springframework.data.domain.Limit;
2829
import org.springframework.data.domain.Pageable;
2930
import org.springframework.data.domain.ScrollPosition;
3031
import org.springframework.data.domain.Sort;
@@ -58,7 +59,7 @@ public class Parameter {
5859

5960
static {
6061

61-
List<Class<?>> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class));
62+
List<Class<?>> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class, Limit.class));
6263

6364
// consider Kotlin Coroutines Continuation a special parameter. That parameter is synthetic and should not get
6465
// bound to any query.
@@ -221,6 +222,16 @@ boolean isSort() {
221222
return Sort.class.isAssignableFrom(getType());
222223
}
223224

225+
/**
226+
* Returns whether the {@link Parameter} is a {@link Limit} parameter.
227+
*
228+
* @return
229+
* @since 3.2
230+
*/
231+
boolean isLimit() {
232+
return Limit.class.isAssignableFrom(getType());
233+
}
234+
224235
/**
225236
* Returns whether the given {@link MethodParameter} is a dynamic projection parameter, which means it carries a
226237
* dynamic type parameter which is identical to the type parameter of the actually returned type.

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

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.Iterator;
1919

20+
import org.springframework.data.domain.Limit;
2021
import org.springframework.data.domain.Pageable;
2122
import org.springframework.data.domain.ScrollPosition;
2223
import org.springframework.data.domain.Sort;
@@ -54,6 +55,22 @@ public interface ParameterAccessor extends Iterable<Object> {
5455
*/
5556
Sort getSort();
5657

58+
/**
59+
* Returns the {@link Limit} instance to be used for query creation. If no {@link java.lang.reflect.Parameter}
60+
* assignable to {@link Limit} can be found {@link Limit} will be created out of {@link Pageable#getPageSize()} if
61+
* present.
62+
*
63+
* @return {@link Limit#unlimited()} by default.
64+
* @since 3.2
65+
*/
66+
default Limit getLimit() {
67+
68+
if (getPageable().isUnpaged()) {
69+
return Limit.unlimited();
70+
}
71+
return Limit.of(getPageable().getPageSize());
72+
}
73+
5774
/**
5875
* Returns the dynamic projection type to be used when executing the query or {@literal null} if none is defined.
5976
*

0 commit comments

Comments
 (0)