diff --git a/pom.xml b/pom.xml
index 7227da3581..3cfbfad13e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
pom
Spring Data MongoDB
diff --git a/spring-data-mongodb-benchmarks/pom.xml b/spring-data-mongodb-benchmarks/pom.xml
index 9baccaa905..0271160e84 100644
--- a/spring-data-mongodb-benchmarks/pom.xml
+++ b/spring-data-mongodb-benchmarks/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb-cross-store/pom.xml b/spring-data-mongodb-cross-store/pom.xml
index 47a5b7aba7..503a52449e 100644
--- a/spring-data-mongodb-cross-store/pom.xml
+++ b/spring-data-mongodb-cross-store/pom.xml
@@ -6,7 +6,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
../pom.xml
@@ -50,7 +50,7 @@
org.springframework.data
spring-data-mongodb
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
diff --git a/spring-data-mongodb-distribution/pom.xml b/spring-data-mongodb-distribution/pom.xml
index e5c865ea08..69c7bcd3e7 100644
--- a/spring-data-mongodb-distribution/pom.xml
+++ b/spring-data-mongodb-distribution/pom.xml
@@ -13,7 +13,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/pom.xml b/spring-data-mongodb/pom.xml
index b86dc2808c..364f47a933 100644
--- a/spring-data-mongodb/pom.xml
+++ b/spring-data-mongodb/pom.xml
@@ -11,7 +11,7 @@
org.springframework.data
spring-data-mongodb-parent
- 2.1.0.BUILD-SNAPSHOT
+ 2.1.0.DATAMONGO-1979-SNAPSHOT
../pom.xml
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
index c1d1424ce9..3e04d56ba5 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Query.java
@@ -77,4 +77,22 @@
* @return
*/
boolean delete() default false;
+
+ /**
+ * Defines a default sort order for the given query.
+ * NOTE The so set defaults can be altered / overwritten via an explicit
+ * {@link org.springframework.data.domain.Sort} argument of the query method.
+ *
+ *
+ *
+ *
+ * @Query(sort = "{ age : -1 }") // order by age descending
+ * List findByFirstname(String firstname);
+ *
+ *
+ *
+ * @return
+ * @since 2.1
+ */
+ String sort() default "";
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
index 27c7066887..bbb77a9b94 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.query;
+import org.bson.Document;
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
@@ -84,6 +85,7 @@ public Object execute(Object[] parameters) {
Query query = createQuery(accessor);
applyQueryMetaAttributesWhenPresent(query);
+ query = applyAnnotatedDefaultSortIfPresent(query);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor);
Class> typeToRead = processor.getReturnedType().getTypeToRead();
@@ -110,7 +112,7 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
} else if (method.isStreamQuery()) {
return q -> operation.matching(q).stream();
} else if (method.isCollectionQuery()) {
- return q -> operation.matching(q.with(accessor.getPageable())).all();
+ return q -> operation.matching(q.with(accessor.getPageable()).with(accessor.getSort())).all();
} else if (method.isPageQuery()) {
return new PagedExecution(operation, accessor.getPageable());
} else if (isCountQuery()) {
@@ -135,6 +137,23 @@ Query applyQueryMetaAttributesWhenPresent(Query query) {
return query;
}
+ /**
+ * Add a default sort derived from {@link org.springframework.data.mongodb.repository.Query#sort()} to the given
+ * {@link Query} if present.
+ *
+ * @param query the {@link Query} to potentially apply the sort to.
+ * @return the query with potential default sort applied.
+ * @since 2.1
+ */
+ Query applyAnnotatedDefaultSortIfPresent(Query query) {
+
+ if (!method.hasAnnotatedSort()) {
+ return query;
+ }
+
+ return QueryUtils.sneakInDefaultSort(query, Document.parse(method.getAnnotatedSort()));
+ }
+
/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
index 2cd617dadb..53427726a1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
@@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.query;
+import org.bson.Document;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -109,6 +110,7 @@ private Object execute(MongoParameterAccessor parameterAccessor) {
Query query = createQuery(new ConvertingParameterAccessor(operations.getConverter(), parameterAccessor));
applyQueryMetaAttributesWhenPresent(query);
+ query = applyAnnotatedDefaultSortIfPresent(query);
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
Class> typeToRead = processor.getReturnedType().getTypeToRead();
@@ -177,6 +179,23 @@ Query applyQueryMetaAttributesWhenPresent(Query query) {
return query;
}
+ /**
+ * Add a default sort derived from {@link org.springframework.data.mongodb.repository.Query#sort()} to the given
+ * {@link Query} if present.
+ *
+ * @param query the {@link Query} to potentially apply the sort to.
+ * @return the query with potential default sort applied.
+ * @since 2.1
+ */
+ Query applyAnnotatedDefaultSortIfPresent(Query query) {
+
+ if (!method.hasAnnotatedSort()) {
+ return query;
+ }
+
+ return QueryUtils.sneakInDefaultSort(query, Document.parse(method.getAnnotatedSort()));
+ }
+
/**
* Creates a {@link Query} instance using the given {@link ConvertingParameterAccessor}. Will delegate to
* {@link #createQuery(ConvertingParameterAccessor)} by default but allows customization of the count query to be
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
index e0a0184f5f..2aa8e8edf1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java
@@ -16,13 +16,14 @@
package org.springframework.data.mongodb.repository.query;
import java.io.Serializable;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import org.springframework.core.annotation.AnnotatedElementUtils;
-import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.geo.GeoPage;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.GeoResults;
@@ -40,6 +41,7 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@@ -57,6 +59,7 @@ public class MongoQueryMethod extends QueryMethod {
private final Method method;
private final MappingContext extends MongoPersistentEntity>, MongoPersistentProperty> mappingContext;
+ private final Map, Optional> annotationCache;
private @Nullable MongoEntityMetadata> metadata;
@@ -77,6 +80,7 @@ public MongoQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFa
this.method = method;
this.mappingContext = mappingContext;
+ this.annotationCache = new ConcurrentReferenceHashMap();
}
/*
@@ -110,9 +114,8 @@ String getAnnotatedQuery() {
private Optional findAnnotatedQuery() {
- return Optional.ofNullable(getQueryAnnotation()) //
- .map(AnnotationUtils::getValue) //
- .map(it -> (String) it) //
+ return lookupQueryAnnotation() //
+ .map(Query::value) //
.filter(StringUtils::hasText);
}
@@ -123,8 +126,8 @@ private Optional findAnnotatedQuery() {
*/
String getFieldSpecification() {
- return Optional.ofNullable(getQueryAnnotation()) //
- .map(it -> (String) AnnotationUtils.getValue(it, "fields")) //
+ return lookupQueryAnnotation() //
+ .map(Query::fields) //
.filter(StringUtils::hasText) //
.orElse(null);
}
@@ -207,7 +210,11 @@ private boolean isGeoNearQuery(Method method) {
*/
@Nullable
Query getQueryAnnotation() {
- return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
+ return lookupQueryAnnotation().orElse(null);
+ }
+
+ Optional lookupQueryAnnotation() {
+ return doFindAnnotation(Query.class);
}
TypeInformation> getReturnType() {
@@ -230,7 +237,7 @@ public boolean hasQueryMetaAttributes() {
*/
@Nullable
Meta getMetaAnnotation() {
- return AnnotatedElementUtils.findMergedAnnotation(method, Meta.class);
+ return doFindAnnotation(Meta.class).orElse(null);
}
/**
@@ -241,7 +248,7 @@ Meta getMetaAnnotation() {
*/
@Nullable
Tailable getTailableAnnotation() {
- return AnnotatedElementUtils.findMergedAnnotation(method, Tailable.class);
+ return doFindAnnotation(Tailable.class).orElse(null);
}
/**
@@ -283,4 +290,34 @@ public org.springframework.data.mongodb.core.query.Meta getQueryMetaAttributes()
return metaAttributes;
}
+
+ /**
+ * Check if the query method is decorated with an non empty {@link Query#sort()}.
+ *
+ * @return true if method annotated with {@link Query} having an non empty sort attribute.
+ * @since 2.1
+ */
+ public boolean hasAnnotatedSort() {
+ return lookupQueryAnnotation().map(it -> !it.sort().isEmpty()).orElse(false);
+ }
+
+ /**
+ * Get the sort value, used as default, extracted from the {@link Query} annotation.
+ *
+ * @return the {@link Query#sort()} value.
+ * @throws IllegalStateException if method not annotated with {@link Query}. Make sure to check
+ * {@link #hasAnnotatedQuery()} first.
+ * @since 2.1
+ */
+ public String getAnnotatedSort() {
+
+ return lookupQueryAnnotation().map(Query::sort).orElseThrow(() -> new IllegalStateException(
+ "Expected to find @Query annotation but did not. Make sure to check hasAnnotatedSort() before."));
+ }
+
+ private Optional doFindAnnotation(Class annotationType) {
+
+ return (Optional) this.annotationCache.computeIfAbsent(annotationType,
+ it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
+ }
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java
new file mode 100644
index 0000000000..68d953ca77
--- /dev/null
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/QueryUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://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.mongodb.repository.query;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.bson.Document;
+import org.springframework.aop.framework.ProxyFactory;
+import org.springframework.data.mongodb.core.query.Query;
+
+/**
+ * Internal utility class to help avoid duplicate code required in both the reactive and the sync {@link Query} support
+ * offered by repositories.
+ *
+ * @author Christoph Strobl
+ * @since 2.1
+ * @currentRead Assassin's Apprentice - Robin Hobb
+ */
+class QueryUtils {
+
+ /**
+ * Add a default sort expression to the given Query. Attributes of the given {@code sort} may be overwritten by the
+ * sort explicitly defined by the {@link Query} itself.
+ *
+ * @param query the {@link Query} to decorate.
+ * @param defaultSort the default sort expression to apply to the query.
+ * @return the query having the given {@code sort} applied.
+ */
+ static Query sneakInDefaultSort(Query query, Document defaultSort) {
+
+ if (defaultSort.isEmpty()) {
+ return query;
+ }
+
+ ProxyFactory factory = new ProxyFactory(query);
+ factory.addAdvice((MethodInterceptor) invocation -> {
+
+ if (!invocation.getMethod().getName().equals("getSortObject")) {
+ return invocation.proceed();
+ }
+
+ Document combinedSort = new Document(defaultSort);
+ combinedSort.putAll((Document) invocation.proceed());
+ return combinedSort;
+ });
+
+ return (Query) factory.getProxy();
+ }
+}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
index e650c38378..9f19b8af27 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
@@ -1205,4 +1205,15 @@ public void findSingleEntityThrowsErrorWhenNotUnique() {
public void findOptionalSingleEntityThrowsErrorWhenNotUnique() {
repository.findOptionalPersonByLastnameLike(dave.getLastname());
}
+
+ @Test // DATAMONGO-1979
+ public void findAppliesAnnotatedSort() {
+ assertThat(repository.findByAgeGreaterThan(40)).containsExactly(carter, boyd, dave, leroi);
+ }
+
+ @Test // DATAMONGO-1979
+ public void findWithSortOverwritesAnnotatedSort() {
+ assertThat(repository.findByAgeGreaterThan(40, Sort.by(Direction.ASC, "age"))).containsExactly(leroi, dave, boyd,
+ carter);
+ }
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
index 44ca0f0b5c..7745b524df 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
@@ -347,4 +347,10 @@ Page findByCustomQueryLastnameAndAddressStreetInList(String lastname, Li
// DATAMONGO-1752
Iterable findClosedProjectionBy();
+
+ @Query(sort = "{ age : -1 }")
+ List findByAgeGreaterThan(int age);
+
+ @Query(sort = "{ age : -1 }")
+ List findByAgeGreaterThan(int age, Sort sort);
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java
index 3f6948dc06..ade137d1b1 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java
@@ -20,6 +20,8 @@
import static org.springframework.data.domain.Sort.Direction.*;
import lombok.NoArgsConstructor;
+import org.hamcrest.collection.IsIterableContainingInOrder;
+import org.springframework.data.domain.Sort.Direction;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -307,6 +309,23 @@ public void shouldReturnFirstFindFirstWithMoreResults() {
StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete();
}
+ @Test // DATAMONGO-1979
+ public void findAppliesAnnotatedSort() {
+
+ repository.findByAgeGreaterThan(40).collectList().as(StepVerifier::create).consumeNextWith(result -> {
+ assertThat(result, IsIterableContainingInOrder.contains(carter, boyd, dave, leroi));
+ });
+ }
+
+ @Test // DATAMONGO-1979
+ public void findWithSortOverwritesAnnotatedSort() {
+
+ repository.findByAgeGreaterThan(40, Sort.by(Direction.ASC, "age")).collectList().as(StepVerifier::create)
+ .consumeNextWith(result -> {
+ assertThat(result, IsIterableContainingInOrder.contains(leroi, dave, boyd, carter));
+ });
+ }
+
interface ReactivePersonRepository extends ReactiveMongoRepository {
Flux findByLastname(String lastname);
@@ -335,6 +354,12 @@ interface ReactivePersonRepository extends ReactiveMongoRepository findPersonByLocationNear(Point point, Distance maxDistance);
Mono findFirstByLastname(String lastname);
+
+ @Query(sort = "{ age : -1 }")
+ Flux findByAgeGreaterThan(int age);
+
+ @Query(sort = "{ age : -1 }")
+ Flux findByAgeGreaterThan(int age, Sort sort);
}
interface ReactiveCappedCollectionRepository extends Repository {
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
index 6f28dee9f9..7ec17a4c58 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
@@ -17,8 +17,9 @@
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
@@ -39,6 +40,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
@@ -284,6 +286,28 @@ public void doesNotFixCollectionOnPreparation() {
verify(executableFind).as(DynamicallyMapped.class);
}
+ @Test // DATAMONGO-1979
+ public void usesAnnotatedSortWhenPresent() {
+
+ createQueryForMethod("findByAge", Integer.class) //
+ .execute(new Object[] { 1000 });
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class);
+ verify(withQueryMock).matching(captor.capture());
+ assertThat(captor.getValue().getSortObject(), is(equalTo(new Document("age", 1))));
+ }
+
+ @Test // DATAMONGO-1979
+ public void usesExplicitSortOverridesAnnotatedSortWhenPresent() {
+
+ createQueryForMethod("findByAge", Integer.class, Sort.class) //
+ .execute(new Object[] { 1000, Sort.by(Direction.DESC, "age") });
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class);
+ verify(withQueryMock).matching(captor.capture());
+ assertThat(captor.getValue().getSortObject(), is(equalTo(new Document("age", -1))));
+ }
+
private MongoQueryFake createQueryForMethod(String methodName, Class>... paramTypes) {
return createQueryForMethod(Repo.class, methodName, paramTypes);
}
@@ -370,6 +394,12 @@ private interface Repo extends MongoRepository {
Optional findByLastname(String lastname);
Person findFirstByLastname(String lastname);
+
+ @org.springframework.data.mongodb.repository.Query(sort = "{ age : 1 }")
+ List findByAge(Integer age);
+
+ @org.springframework.data.mongodb.repository.Query(sort = "{ age : 1 }")
+ List findByAge(Integer age, Sort page);
}
// DATAMONGO-1872
diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc
index 83cb05884e..93c00db0c2 100644
--- a/src/main/asciidoc/reference/mongo-repositories.adoc
+++ b/src/main/asciidoc/reference/mongo-repositories.adoc
@@ -395,6 +395,36 @@ public interface PersonRepository extends MongoRepository
The query in the preceding example returns only the `firstname`, `lastname` and `Id` properties of the `Person` objects. The `age` property, a `java.lang.Integer`, is not set and its value is therefore null.
+[[mongodb.repositories.queries.sort]]
+=== Sorting Query Method results
+
+When it comes to sorting MongoDB query results via the repository interface there are several options as listed below.
+
+.Sorting query results
+====
+[source,java]
+----
+public interface PersonRepository extends MongoRepository {
+
+ List findByFirstnameSortByAgeDesc(String firstname); <1>
+
+ List findByFirstname(String firstname, Sort sort); <2>
+
+ @Query(sort = "{ age : -1 }")
+ List findByFirstname(String firstname); <3>
+
+ @Query(sort = "{ age : -1 }")
+ List findByLastname(String lastname, Sort sort); <4>
+}
+----
+<1> Fixed sorting derived from method name. `SortByAgeDesc` results in `{ age : -1 }` sort parameter.
+<2> Dynamic sorting via method argument. `Sort.by(DESC, "age")` creates a `{ age : -1 }` sort parameter.
+<3> Fixed sorting via `Query` annotation. Sort parameter applied as stated in the `sort` attribute.
+<4> Default sorting via `Query` annotation combined with dynamic one via method argument. `Sort.unsorted()`
+results in `{ age : -1 }`. Using `Sort.by(ASC, "age")` overrides the defaults and creates `{ age : 1 }`. `Sort.by
+(ASC, "firstname")` alters the default and results in `{ age : -1, firstname : 1 }`.
+====
+
[[mongodb.repositories.queries.json-spel]]
=== JSON-based Queries with SpEL Expressions