diff --git a/pom.xml b/pom.xml
index 5df738c14f..80c1b6444c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.dataspring-data-commons
- 3.2.0-SNAPSHOT
+ 3.2.x-2827-SNAPSHOTSpring Data CoreCore Spring concepts underpinning every Spring Data module.
diff --git a/src/main/asciidoc/repositories-paging-sorting.adoc b/src/main/asciidoc/repositories-paging-sorting.adoc
index 58b94fe867..9f372c652c 100644
--- a/src/main/asciidoc/repositories-paging-sorting.adoc
+++ b/src/main/asciidoc/repositories-paging-sorting.adoc
@@ -1,12 +1,12 @@
[[repositories.special-parameters]]
-=== Paging, Iterating Large Results, Sorting
+=== Paging, Iterating Large Results, Sorting & Limiting
To handle parameters in your query, define method parameters as already seen in the preceding examples.
-Besides that, the infrastructure recognizes certain specific types like `Pageable` and `Sort`, to apply pagination and sorting to your queries dynamically.
+Besides that, the infrastructure recognizes certain specific types like `Pageable`, `Sort` and `Limit`, to apply pagination, sorting and limiting to your queries dynamically.
The following example demonstrates these features:
ifdef::feature-scroll[]
-.Using `Pageable`, `Slice`, `ScrollPosition`, and `Sort` in query methods
+.Using `Pageable`, `Slice`, `ScrollPosition`, `Sort` and `Limit` in query methods
====
[source,java]
----
@@ -18,13 +18,15 @@ Window findTop10ByLastname(String lastname, ScrollPosition position, Sort
List findByLastname(String lastname, Sort sort);
+List findByLastname(String lastname, Sort sort, Limit limit);
+
List findByLastname(String lastname, Pageable pageable);
----
====
endif::[]
ifndef::feature-scroll[]
-.Using `Pageable`, `Slice`, and `Sort` in query methods
+.Using `Pageable`, `Slice`, `Sort` and `Limit` in query methods
====
[source,java]
----
@@ -34,13 +36,15 @@ Slice findByLastname(String lastname, Pageable pageable);
List findByLastname(String lastname, Sort sort);
+List findByLastname(String lastname, Sort sort, Limit limit);
+
List findByLastname(String lastname, Pageable pageable);
----
====
endif::[]
-IMPORTANT: APIs taking `Sort` and `Pageable` expect non-`null` values to be handed into methods.
-If you do not want to apply any sorting or pagination, use `Sort.unsorted()` and `Pageable.unpaged()`.
+IMPORTANT: APIs taking `Sort`, `Pageable` and `Limit` expect non-`null` values to be handed into methods.
+If you do not want to apply any sorting or pagination, use `Sort.unsorted()`, `Pageable.unpaged()` and `Limit.unlimited()`.
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.
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.
NOTE: To find out how many pages you get for an entire query, you have to trigger an additional count query.
By default, this query is derived from the query you actually trigger.
+[IMPORTANT]
+====
+Special parameters may only be used once within a query method. +
+Some of the special parameters described above are mutually exclusive.
+Please consider the following list of invalid parameter combinations.
+
+|===
+| Parameters | Example | Reason
+
+| `Pageable` & `Sort`
+| `findBy...(Pageable page, Sort sort)`
+| `Pageable` already defines `Sort`
+
+| `Pageable` & `Limit`
+| `findBy...(Pageable page, Limit limit)`
+| `Pageable` already defines a limit.
+
+|===
+
+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.
+====
+
[[repositories.scrolling.guidance]]
==== Which Method is Appropriate?
diff --git a/src/main/java/org/springframework/data/domain/Limit.java b/src/main/java/org/springframework/data/domain/Limit.java
new file mode 100644
index 0000000000..9cd68cf503
--- /dev/null
+++ b/src/main/java/org/springframework/data/domain/Limit.java
@@ -0,0 +1,121 @@
+/*
+ * 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 org.springframework.data.domain.Limit.Limited;
+import org.springframework.data.domain.Limit.Unlimited;
+import org.springframework.util.ClassUtils;
+
+/**
+ * {@link Limit} represents the maximum value up to which an operation should continue processing. It may be used for
+ * defining the {@link #max() maximum} number of results within a repository finder method or if applicable a template
+ * operation.
+ *
+ * A {@link Limit#isUnlimited()} is used to indicate that there is no {@link Limit} defined, which should be favoured
+ * over using {@literal null} or {@link java.util.Optional#empty()} to indicate the absence of an actual {@link Limit}.
+ *
+ * {@link Limit} itself does not make assumptions about the actual {@link #max()} value sign. This means that a negative
+ * value may be valid within a defined context. Implementations should override {@link #isUnlimited()} to fit their
+ * needs and enforce restrictions if needed.
+ *
+ * @author Christoph Strobl
+ * @since 3.2
+ */
+public sealed interface Limit permits Limited, Unlimited {
+
+ /**
+ * @return the max number of potential results.
+ */
+ int max();
+
+ /**
+ * @return {@literal true} if no limiting (maximum value) should be applied.
+ */
+ default boolean isUnlimited() {
+ return Unlimited.INSTANCE.equals(this);
+ }
+
+ /**
+ * @return a {@link Limit} instance that does not define {@link #max()} and answers {@link #isUnlimited()} with
+ * {@literal true}.
+ */
+ static Limit unlimited() {
+ return Unlimited.INSTANCE;
+ }
+
+ /**
+ * Create a new {@link Limit} from the given {@literal max} value.
+ *
+ * @param max the maximum value.
+ * @return new instance of {@link Limit}.
+ */
+ static Limit of(int max) {
+ return new Limited(max);
+ }
+
+ final class Limited implements Limit {
+
+ private final int max;
+
+ Limited(int max) {
+ this.max = max;
+ }
+
+ @Override
+ public int max() {
+ return max;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (obj == null) {
+ return false;
+ }
+ if (!ClassUtils.isAssignable(Limit.class, obj.getClass())) {
+ return false;
+ }
+ Limit that = (Limit) obj;
+ if (this.isUnlimited() && that.isUnlimited()) {
+ return true;
+ }
+ return max() == that.max();
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) (max ^ (max >>> 32));
+ }
+ }
+
+ final class Unlimited implements Limit {
+
+ static final Limit INSTANCE = new Unlimited();
+
+ Unlimited() {}
+
+ @Override
+ public int max() {
+ throw new IllegalStateException(
+ "Unlimited does not define 'max'. Please check 'isUnlimited' before attempting to read 'max'");
+ }
+
+ @Override
+ public boolean isUnlimited() {
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/domain/Pageable.java b/src/main/java/org/springframework/data/domain/Pageable.java
index ff830f92f6..08f1dca736 100644
--- a/src/main/java/org/springframework/data/domain/Pageable.java
+++ b/src/main/java/org/springframework/data/domain/Pageable.java
@@ -24,6 +24,7 @@
*
* @author Oliver Gierke
* @author Mark Paluch
+ * @author Christoph Strobl
*/
public interface Pageable {
@@ -33,7 +34,18 @@ public interface Pageable {
* @return
*/
static Pageable unpaged() {
- return Unpaged.INSTANCE;
+ return unpaged(Sort.unsorted());
+ }
+
+ /**
+ * Returns a {@link Pageable} instance representing no pagination setup having a defined result {@link Sort order}.
+ *
+ * @param sort must not be {@literal null}, use {@link Sort#unsorted()} if needed.
+ * @return never {@literal null}.
+ * @since 3.2
+ */
+ static Pageable unpaged(Sort sort) {
+ return Unpaged.sorted(sort);
}
/**
diff --git a/src/main/java/org/springframework/data/domain/Unpaged.java b/src/main/java/org/springframework/data/domain/Unpaged.java
index b66707c73c..49607a0efd 100644
--- a/src/main/java/org/springframework/data/domain/Unpaged.java
+++ b/src/main/java/org/springframework/data/domain/Unpaged.java
@@ -19,10 +19,21 @@
* {@link Pageable} implementation to represent the absence of pagination information.
*
* @author Oliver Gierke
+ * @author Christoph Strobl
*/
-enum Unpaged implements Pageable {
+final class Unpaged implements Pageable {
- INSTANCE;
+ private static final Pageable UNSORTED = new Unpaged(Sort.unsorted());
+
+ private final Sort sort;
+
+ Unpaged(Sort sort) {
+ this.sort = sort;
+ }
+
+ static Pageable sorted(Sort sort) {
+ return sort.isSorted() ? new Unpaged(sort) : UNSORTED;
+ }
@Override
public boolean isPaged() {
@@ -46,7 +57,7 @@ public boolean hasPrevious() {
@Override
public Sort getSort() {
- return Sort.unsorted();
+ return sort;
}
@Override
@@ -78,5 +89,4 @@ public Pageable withPage(int pageNumber) {
throw new UnsupportedOperationException();
}
-
}
diff --git a/src/main/java/org/springframework/data/repository/query/Parameter.java b/src/main/java/org/springframework/data/repository/query/Parameter.java
index c751db67c5..385fca0b50 100644
--- a/src/main/java/org/springframework/data/repository/query/Parameter.java
+++ b/src/main/java/org/springframework/data/repository/query/Parameter.java
@@ -25,6 +25,7 @@
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
+import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort;
@@ -58,7 +59,7 @@ public class Parameter {
static {
- List> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class));
+ List> types = new ArrayList<>(Arrays.asList(ScrollPosition.class, Pageable.class, Sort.class, Limit.class));
// consider Kotlin Coroutines Continuation a special parameter. That parameter is synthetic and should not get
// bound to any query.
@@ -221,6 +222,16 @@ boolean isSort() {
return Sort.class.isAssignableFrom(getType());
}
+ /**
+ * Returns whether the {@link Parameter} is a {@link Limit} parameter.
+ *
+ * @return
+ * @since 3.2
+ */
+ boolean isLimit() {
+ return Limit.class.isAssignableFrom(getType());
+ }
+
/**
* Returns whether the given {@link MethodParameter} is a dynamic projection parameter, which means it carries a
* dynamic type parameter which is identical to the type parameter of the actually returned type.
diff --git a/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java b/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java
index 369c781a56..c009dda688 100644
--- a/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java
+++ b/src/main/java/org/springframework/data/repository/query/ParameterAccessor.java
@@ -17,6 +17,7 @@
import java.util.Iterator;
+import org.springframework.data.domain.Limit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.domain.Sort;
@@ -54,6 +55,22 @@ public interface ParameterAccessor extends Iterable