Skip to content

DATAMONGO-1979 - Add support for @Query(sort = "… #566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-mongodb-cross-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down Expand Up @@ -50,7 +50,7 @@
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
</dependency>

<!-- reactive -->
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.1.0.BUILD-SNAPSHOT</version>
<version>2.1.0.DATAMONGO-1979-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,22 @@
* @return
*/
boolean delete() default false;

/**
* Defines a default sort order for the given query.<br />
* <strong>NOTE</strong> The so set defaults can be altered / overwritten via an explicit
* {@link org.springframework.data.domain.Sort} argument of the query method.
*
* <pre>
* <code>
*
* &#64;Query(sort = "{ age : -1 }") // order by age descending
* List<Person> findByFirstname(String firstname);
* </code>
* </pre>
*
* @return
* @since 2.1
*/
String sort() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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()) {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -57,6 +59,7 @@ public class MongoQueryMethod extends QueryMethod {

private final Method method;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache;

private @Nullable MongoEntityMetadata<?> metadata;

Expand All @@ -77,6 +80,7 @@ public MongoQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFa

this.method = method;
this.mappingContext = mappingContext;
this.annotationCache = new ConcurrentReferenceHashMap();
}

/*
Expand Down Expand Up @@ -110,9 +114,8 @@ String getAnnotatedQuery() {

private Optional<String> findAnnotatedQuery() {

return Optional.ofNullable(getQueryAnnotation()) //
.map(AnnotationUtils::getValue) //
.map(it -> (String) it) //
return lookupQueryAnnotation() //
.map(Query::value) //
.filter(StringUtils::hasText);
}

Expand All @@ -123,8 +126,8 @@ private Optional<String> findAnnotatedQuery() {
*/
String getFieldSpecification() {

return Optional.ofNullable(getQueryAnnotation()) //
.map(it -> (String) AnnotationUtils.getValue(it, "fields")) //
return lookupQueryAnnotation() //
.map(Query::fields) //
.filter(StringUtils::hasText) //
.orElse(null);
}
Expand Down Expand Up @@ -207,7 +210,11 @@ private boolean isGeoNearQuery(Method method) {
*/
@Nullable
Query getQueryAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Query.class);
return lookupQueryAnnotation().orElse(null);
}

Optional<Query> lookupQueryAnnotation() {
return doFindAnnotation(Query.class);
}

TypeInformation<?> getReturnType() {
Expand All @@ -230,7 +237,7 @@ public boolean hasQueryMetaAttributes() {
*/
@Nullable
Meta getMetaAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Meta.class);
return doFindAnnotation(Meta.class).orElse(null);
}

/**
Expand All @@ -241,7 +248,7 @@ Meta getMetaAnnotation() {
*/
@Nullable
Tailable getTailableAnnotation() {
return AnnotatedElementUtils.findMergedAnnotation(method, Tailable.class);
return doFindAnnotation(Tailable.class).orElse(null);
}

/**
Expand Down Expand Up @@ -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 <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {

return (Optional) this.annotationCache.computeIfAbsent(annotationType,
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,10 @@ Page<Person> findByCustomQueryLastnameAndAddressStreetInList(String lastname, Li

// DATAMONGO-1752
Iterable<PersonSummary> findClosedProjectionBy();

@Query(sort = "{ age : -1 }")
List<Person> findByAgeGreaterThan(int age);

@Query(sort = "{ age : -1 }")
List<Person> findByAgeGreaterThan(int age, Sort sort);
}
Loading