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 3e04d56ba..9967385c4 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 @@ -80,14 +80,14 @@ public @interface Query { /** * Defines a default sort order for the given query.
- * NOTE The so set defaults can be altered / overwritten via an explicit + * NOTE: The so set defaults can be altered / overwritten using 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); 	 
+	 * 		List findByFirstname(String firstname);
 	 * 
 	 * 
* 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 bbb77a9b9..e11a567db 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 @@ -151,7 +151,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { return query; } - return QueryUtils.sneakInDefaultSort(query, Document.parse(method.getAnnotatedSort())); + return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort())); } /** 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 53427726a..f850f3f2f 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 @@ -193,7 +193,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery { return query; } - return QueryUtils.sneakInDefaultSort(query, Document.parse(method.getAnnotatedSort())); + return QueryUtils.decorateSort(query, Document.parse(method.getAnnotatedSort())); } /** 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 2aa8e8edf..eb61f3c85 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 @@ -80,7 +80,7 @@ public class MongoQueryMethod extends QueryMethod { this.method = method; this.mappingContext = mappingContext; - this.annotationCache = new ConcurrentReferenceHashMap(); + this.annotationCache = new ConcurrentReferenceHashMap<>(); } /* @@ -124,6 +124,7 @@ public class MongoQueryMethod extends QueryMethod { * * @return */ + @Nullable String getFieldSpecification() { return lookupQueryAnnotation() // @@ -159,7 +160,7 @@ public class MongoQueryMethod extends QueryMethod { MongoPersistentEntity collectionEntity = domainClass.isAssignableFrom(returnedObjectType) ? returnedEntity : managedEntity; - this.metadata = new SimpleMongoEntityMetadata((Class) returnedEntity.getType(), + this.metadata = new SimpleMongoEntityMetadata<>((Class) returnedEntity.getType(), collectionEntity); } } @@ -315,9 +316,10 @@ public class MongoQueryMethod extends QueryMethod { "Expected to find @Query annotation but did not. Make sure to check hasAnnotatedSort() before.")); } + @SuppressWarnings("unchecked") private Optional doFindAnnotation(Class annotationType) { - return (Optional) this.annotationCache.computeIfAbsent(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 index 68d953ca7..fb938740a 100644 --- 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 @@ -31,14 +31,14 @@ import org.springframework.data.mongodb.core.query.Query; 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. + * Decorate {@link Query} and add a default sort expression to the given {@link 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) { + static Query decorateSort(Query query, Document defaultSort) { if (defaultSort.isEmpty()) { return query; diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java index 0ad0d91d5..91af6b342 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryMethod.java @@ -79,7 +79,7 @@ public class ReactiveMongoQueryMethod extends MongoQueryMethod { ClassUtils.getShortName(method.getDeclaringClass()), method.getName())); } - if (!multiWrapper && !singleWrapperWithWrappedPageableResult) { + if (!multiWrapper) { throw new IllegalStateException(String.format( "Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: %s", method.toString())); 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 ade137d1b..1651bbb3e 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 @@ -15,13 +15,11 @@ */ package org.springframework.data.mongodb.repository; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.assertj.core.api.Assertions.offset; import static org.springframework.data.domain.Sort.Direction.*; +import static org.springframework.data.mongodb.test.util.Assertions.assertThat; 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; @@ -45,6 +43,7 @@ import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.springframework.data.geo.Circle; import org.springframework.data.geo.Distance; import org.springframework.data.geo.GeoResult; @@ -130,12 +129,12 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF @Test // DATAMONGO-1444 public void shouldFindOneByLastName() { - StepVerifier.create(repository.findOneByLastname(carter.getLastname())).expectNext(carter); + StepVerifier.create(repository.findOneByLastname(carter.getLastname())).expectNext(carter).verifyComplete(); } @Test // DATAMONGO-1444 public void shouldFindOneByPublisherOfLastName() { - StepVerifier.create(repository.findByLastname(Mono.just(carter.getLastname()))).expectNext(carter); + StepVerifier.create(repository.findByLastname(Mono.just(carter.getLastname()))).expectNext(carter).verifyComplete(); } @Test // DATAMONGO-1444 @@ -164,6 +163,7 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF @Test // DATAMONGO-1444 public void shouldFindByLastNameAndSort() { + StepVerifier.create(repository.findByLastname("Matthews", Sort.by(ASC, "age"))) // .expectNext(oliver, dave) // .verifyComplete(); @@ -188,11 +188,11 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF Disposable disposable = cappedRepository.findByKey("value").doOnNext(documents::add).subscribe(); - assertThat(documents.poll(5, TimeUnit.SECONDS), is(notNullValue())); + assertThat(documents.poll(5, TimeUnit.SECONDS)).isNotNull(); StepVerifier.create(template.insert(new Capped("value", Math.random()))).expectNextCount(1).verifyComplete(); - assertThat(documents.poll(5, TimeUnit.SECONDS), is(notNullValue())); - assertThat(documents.isEmpty(), is(true)); + assertThat(documents.poll(5, TimeUnit.SECONDS)).isNotNull(); + assertThat(documents).isEmpty(); disposable.dispose(); } @@ -213,16 +213,16 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF Disposable disposable = cappedRepository.findProjectionByKey("value").doOnNext(documents::add).subscribe(); CappedProjection projection1 = documents.poll(5, TimeUnit.SECONDS); - assertThat(projection1, is(notNullValue())); - assertThat(projection1.getRandom(), is(not(0))); + assertThat(projection1).isNotNull(); + assertThat(projection1.getRandom()).isNotEqualTo(0); StepVerifier.create(template.insert(new Capped("value", Math.random()))).expectNextCount(1).verifyComplete(); CappedProjection projection2 = documents.poll(5, TimeUnit.SECONDS); - assertThat(projection2, is(notNullValue())); - assertThat(projection2.getRandom(), is(not(0))); + assertThat(projection2).isNotNull(); + assertThat(projection2.getRandom()).isNotEqualTo(0); - assertThat(documents.isEmpty(), is(true)); + assertThat(documents).isEmpty(); disposable.dispose(); } @@ -263,8 +263,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF new Distance(2000, Metrics.KILOMETERS)) // ).consumeNextWith(actual -> { - assertThat(actual.getDistance().getValue(), is(closeTo(1, 1))); - assertThat(actual.getContent(), is(equalTo(dave))); + assertThat(actual.getDistance().getValue()).isCloseTo(1, offset(1d)); + assertThat(actual.getContent()).isEqualTo(dave); }).verifyComplete(); } @@ -280,8 +280,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF PageRequest.of(0, 10))) // .consumeNextWith(actual -> { - assertThat(actual.getDistance().getValue(), is(closeTo(1, 1))); - assertThat(actual.getContent(), is(equalTo(dave))); + assertThat(actual.getDistance().getValue()).isCloseTo(1, offset(1d)); + assertThat(actual.getContent()).isEqualTo(dave); }).verifyComplete(); } @@ -313,8 +313,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF public void findAppliesAnnotatedSort() { repository.findByAgeGreaterThan(40).collectList().as(StepVerifier::create).consumeNextWith(result -> { - assertThat(result, IsIterableContainingInOrder.contains(carter, boyd, dave, leroi)); - }); + assertThat(result).containsSequence(carter, boyd, dave, leroi); + }).verifyComplete(); } @Test // DATAMONGO-1979 @@ -322,8 +322,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF repository.findByAgeGreaterThan(40, Sort.by(Direction.ASC, "age")).collectList().as(StepVerifier::create) .consumeNextWith(result -> { - assertThat(result, IsIterableContainingInOrder.contains(leroi, dave, boyd, carter)); - }); + assertThat(result).containsSequence(leroi, dave, boyd, carter); + }).verifyComplete(); } interface ReactivePersonRepository extends ReactiveMongoRepository { diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index c6dbbecd1..d3ed101f3 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -13,6 +13,7 @@ * Tailable cursors for imperative driver. * <> support for the imperative and reactive Template APIs. * <> support and a MongoDB-specific transaction manager implementation. +* <> using `@Query(sort=…)`. [[new-features.2-0-0]] == What's New in Spring Data MongoDB 2.0 diff --git a/src/main/asciidoc/reference/mongo-repositories.adoc b/src/main/asciidoc/reference/mongo-repositories.adoc index 93c00db0c..5087354a8 100644 --- a/src/main/asciidoc/reference/mongo-repositories.adoc +++ b/src/main/asciidoc/reference/mongo-repositories.adoc @@ -398,9 +398,9 @@ The query in the preceding example returns only the `firstname`, `lastname` and [[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. +MongoDB repositories allow various approaches to define sorting order. Let's take a look at the following example: -.Sorting query results +.Sorting Query Results ==== [source,java] ---- @@ -417,10 +417,10 @@ public interface PersonRepository extends MongoRepository { 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()` +<1> Static sorting derived from method name. `SortByAgeDesc` results in `{ age : -1 }` for the sort parameter. +<2> Dynamic sorting using a method argument. `Sort.by(DESC, "age")` creates `{ age : -1 }` for the sort parameter. +<3> Static sorting via `Query` annotation. Sort parameter applied as stated in the `sort` attribute. +<4> Default sorting via `Query` annotation combined with dynamic one via a 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 }`. ====