diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Aggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Aggregation.java
index 0c74fea64..eea5b0118 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Aggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/Aggregation.java
@@ -25,12 +25,13 @@ import org.springframework.core.annotation.AliasFor;
import org.springframework.data.annotation.QueryAnnotation;
/**
- * The {@link Aggregation} annotation can be used to decorate a {@link org.springframework.data.repository.Repository}
- * query method so that it runs the {@link Aggregation#pipeline()} on invocation.
- * The pipeline stages are mapped against the {@link org.springframework.data.repository.Repository} domain type to
- * consider {@link org.springframework.data.mongodb.core.mapping.Field field} mappings and may contain simple
- * placeholders {@code ?0} as well as {@link org.springframework.expression.spel.standard.SpelExpression
- * SpelExpressions}.
+ * The {@link Aggregation} annotation can be used to annotate a {@link org.springframework.data.repository.Repository}
+ * query method so that it runs the {@link Aggregation#pipeline()} on invocation.
+ *
+ * Pipeline stages are mapped against the {@link org.springframework.data.repository.Repository} domain type to consider
+ * {@link org.springframework.data.mongodb.core.mapping.Field field} mappings and may contain simple placeholders
+ * {@code ?0} as well as {@link org.springframework.expression.spel.standard.SpelExpression SpelExpressions}.
+ *
* Query method {@link org.springframework.data.domain.Sort} and {@link org.springframework.data.domain.Pageable}
* arguments are applied at the end of the pipeline or can be defined manually as part of it.
*
@@ -121,7 +122,6 @@ public @interface Aggregation {
*
*
* @return an empty {@link String} by default.
- * @since 2.2
*/
String collation() default "";
}
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java
index 3fe02a4af..3c71a651e 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AggregationUtils.java
@@ -62,7 +62,6 @@ class AggregationUtils {
* @see AggregationOptions#getCollation()
* @see CollationUtils#computeCollation(String, ConvertingParameterAccessor, MongoParameters, SpelExpressionParser,
* QueryMethodEvaluationContextProvider)
- * @since 2.2
*/
static AggregationOptions.Builder applyCollation(AggregationOptions.Builder builder,
@Nullable String collationExpression, ConvertingParameterAccessor accessor, MongoParameters parameters,
@@ -79,7 +78,7 @@ class AggregationUtils {
* {@link ParameterBindingDocumentCodec} to obtain the MongoDB native {@link Document} representation returned by
* {@link AggregationOperation#toDocument(AggregationOperationContext)} that is mapped against the domain type
* properties.
- *
+ *
* @param method
* @param accessor
* @param expressionParser
@@ -94,7 +93,7 @@ class AggregationUtils {
List target = new ArrayList<>(method.getAnnotatedAggregation().length);
for (String source : method.getAnnotatedAggregation()) {
- target.add(ctx -> ctx.getMappedObject(CODEC.decode(source, bindingContext), method.getRepositoryDomainType()));
+ target.add(ctx -> ctx.getMappedObject(CODEC.decode(source, bindingContext), method.getDomainClass()));
}
return target;
}
@@ -174,16 +173,16 @@ class AggregationUtils {
return getPotentiallyConvertedSimpleTypeValue(converter, source.values().iterator().next(), targetType);
}
- Document tmp = new Document(source);
- tmp.remove("_id");
+ Document intermediate = new Document(source);
+ intermediate.remove("_id");
- if (tmp.size() == 1) {
- return getPotentiallyConvertedSimpleTypeValue(converter, tmp.values().iterator().next(), targetType);
+ if (intermediate.size() == 1) {
+ return getPotentiallyConvertedSimpleTypeValue(converter, intermediate.values().iterator().next(), targetType);
}
- for (Map.Entry entry : tmp.entrySet()) {
+ for (Map.Entry entry : intermediate.entrySet()) {
if (entry != null && ClassUtils.isAssignable(targetType, entry.getValue().getClass())) {
- return (T) entry.getValue();
+ return targetType.cast(entry.getValue());
}
}
@@ -192,14 +191,15 @@ class AggregationUtils {
}
@Nullable
- private static T getPotentiallyConvertedSimpleTypeValue(MongoConverter converter, Object value,
+ @SuppressWarnings("unchecked")
+ private static T getPotentiallyConvertedSimpleTypeValue(MongoConverter converter, @Nullable Object value,
Class targetType) {
if (value == null) {
- return (T) value;
+ return null;
}
- if (!converter.getConversionService().canConvert(value.getClass(), targetType)) {
+ if (ClassUtils.isAssignableValue(targetType, value)) {
return (T) value;
}
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 21e375043..a8d244424 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
@@ -168,15 +168,12 @@ public class MongoQueryMethod extends QueryMethod {
return this.metadata;
}
- /**
- * Get the declared {@link org.springframework.data.repository.Repository} domain type.
- *
- * @return the domain type declared at repository level.
- * @see QueryMethod#getDomainClass()
- * @since 2.2
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.query.QueryMethod#getDomainClass()
*/
- Class> getRepositoryDomainType() {
- return getDomainClass();
+ protected Class> getDomainClass() {
+ return super.getDomainClass();
}
/*
@@ -317,7 +314,7 @@ public class MongoQueryMethod extends QueryMethod {
* @since 2.1
*/
public boolean hasAnnotatedSort() {
- return lookupQueryAnnotation().map(it -> !it.sort().isEmpty()).orElse(false);
+ return lookupQueryAnnotation().map(Query::sort).filter(StringUtils::hasText).isPresent();
}
/**
@@ -338,13 +335,18 @@ public class MongoQueryMethod extends QueryMethod {
* Check if the query method is decorated with an non empty {@link Query#collation()} or or
* {@link Aggregation#collation()}.
*
- * @return true if method annotated with {@link Query} or {@link Aggregation} having an non empty collation attribute.
+ * @return true if method annotated with {@link Query} or {@link Aggregation} having a non-empty collation attribute.
* @since 2.2
*/
public boolean hasAnnotatedCollation() {
- return lookupQueryAnnotation().map(it -> !it.collation().isEmpty())
- .orElseGet(() -> lookupAggregationAnnotation().map(it -> !it.collation().isEmpty()).orElse(false));
+ Optional optionalCollation = lookupQueryAnnotation().map(Query::collation);
+
+ if (!optionalCollation.isPresent()) {
+ optionalCollation = lookupAggregationAnnotation().map(Aggregation::collation);
+ }
+
+ return optionalCollation.filter(StringUtils::hasText).isPresent();
}
/**
@@ -374,15 +376,16 @@ public class MongoQueryMethod extends QueryMethod {
}
/**
- * Returns the query string declared in a {@link Query} annotation or {@literal null} if neither the annotation found
- * nor the attribute was specified.
+ * Returns the aggregation pipeline declared in a {@link Aggregation} annotation.
*
- * @return
+ * @return the aggregation pipeline.
+ * @throws IllegalStateException if method not annotated with {@link Aggregation}. Make sure to check
+ * {@link #hasAnnotatedAggregation()} first.
* @since 2.2
*/
- @Nullable
public String[] getAnnotatedAggregation() {
- return findAnnotatedAggregation().orElse(null);
+ return findAnnotatedAggregation().orElseThrow(() -> new IllegalStateException(
+ "Expected to find @Aggregation annotation but did not. Make sure to check hasAnnotatedAggregation() before."));
}
private Optional findAnnotatedAggregation() {
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java
index 211e8daa2..c362366c1 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregation.java
@@ -20,6 +20,7 @@ import reactor.core.publisher.Flux;
import java.util.List;
import org.bson.Document;
+
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
@@ -27,7 +28,6 @@ import org.springframework.data.mongodb.core.aggregation.AggregationOptions;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
-import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ResultProcessor;
@@ -39,6 +39,7 @@ import org.springframework.util.ClassUtils;
* {@link AggregationOperation aggregation} pipeline to actually execute.
*
* @author Christoph Strobl
+ * @author Mark Paluch
* @since 2.2
*/
public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
@@ -74,10 +75,10 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
protected Object doExecute(ReactiveMongoQueryMethod method, ResultProcessor processor,
ConvertingParameterAccessor accessor, Class> typeToRead) {
- Class> sourceType = method.getRepositoryDomainType();
+ Class> sourceType = method.getDomainClass();
Class> targetType = typeToRead;
- List pipeline = computePipeline(method, accessor);
+ List pipeline = computePipeline(accessor);
AggregationUtils.appendSortIfPresent(pipeline, accessor, typeToRead);
AggregationUtils.appendLimitAndOffsetIfPresent(pipeline, accessor);
@@ -94,17 +95,21 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
Flux> flux = reactiveMongoOperations.aggregate(aggregation, targetType);
if (isSimpleReturnType && !isRawReturnType) {
- return flux.map(it -> AggregationUtils.extractSimpleTypeResult((Document) it, typeToRead, mongoConverter));
+ flux = flux.map(it -> AggregationUtils.extractSimpleTypeResult((Document) it, typeToRead, mongoConverter));
}
- return flux;
+ if (method.isCollectionQuery()) {
+ return flux;
+ } else {
+ return flux.next();
+ }
}
private boolean isSimpleReturnType(Class> targetType) {
return MongoSimpleTypes.HOLDER.isSimpleType(targetType);
}
- List computePipeline(MongoQueryMethod method, ConvertingParameterAccessor accessor) {
+ List computePipeline(ConvertingParameterAccessor accessor) {
return AggregationUtils.computePipeline(getQueryMethod(), accessor, expressionParser, evaluationContextProvider);
}
@@ -122,7 +127,7 @@ public class ReactiveStringBasedAggregation extends AbstractReactiveMongoQuery {
*/
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
- return new BasicQuery("{}");
+ throw new UnsupportedOperationException("No query support for aggregation");
}
/*
diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java
index ebb33e514..ff6fa84b2 100644
--- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java
+++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedAggregation.java
@@ -19,6 +19,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.bson.Document;
+
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
@@ -27,7 +28,6 @@ import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
-import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ResultProcessor;
@@ -71,7 +71,7 @@ public class StringBasedAggregation extends AbstractMongoQuery {
protected Object doExecute(MongoQueryMethod method, ResultProcessor resultProcessor,
ConvertingParameterAccessor accessor, Class> typeToRead) {
- Class> sourceType = method.getRepositoryDomainType();
+ Class> sourceType = method.getDomainClass();
Class> targetType = typeToRead;
List pipeline = computePipeline(method, accessor);
@@ -84,7 +84,7 @@ public class StringBasedAggregation extends AbstractMongoQuery {
if (isSimpleReturnType) {
targetType = Document.class;
} else if (isRawAggregationResult) {
- targetType = method.getReturnType().getActualType().getComponentType().getType();
+ targetType = method.getReturnType().getRequiredActualType().getRequiredComponentType().getType();
}
AggregationOptions options = computeOptions(method, accessor);
@@ -108,13 +108,11 @@ public class StringBasedAggregation extends AbstractMongoQuery {
return result.getMappedResults();
}
- if (isSimpleReturnType) {
+ Object uniqueResult = result.getUniqueMappedResult();
- return AggregationUtils.extractSimpleTypeResult((Document) result.getUniqueMappedResult(), typeToRead,
- mongoConverter);
- }
-
- return result.getUniqueMappedResult();
+ return isSimpleReturnType
+ ? AggregationUtils.extractSimpleTypeResult((Document) uniqueResult, typeToRead, mongoConverter)
+ : uniqueResult;
}
private boolean isSimpleReturnType(Class> targetType) {
@@ -139,7 +137,7 @@ public class StringBasedAggregation extends AbstractMongoQuery {
*/
@Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
- return new BasicQuery("{}");
+ throw new UnsupportedOperationException("No query support for aggregation");
}
/*
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 50e303cc8..444d8e902 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
@@ -1347,7 +1347,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test // DATAMONGO-2153
public void annotatedAggregationWithSingleSimpleResult() {
- assertThat(repository.sumAge()).isInstanceOf(Long.class).isEqualTo(245L);
+ assertThat(repository.sumAge()).isEqualTo(245);
}
@Test // DATAMONGO-2153
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonAggregate.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonAggregate.java
index 58931ddd1..7e4ec3337 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonAggregate.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonAggregate.java
@@ -17,16 +17,29 @@ package org.springframework.data.mongodb.repository;
import lombok.Value;
+import java.util.Collections;
import java.util.List;
import org.springframework.data.annotation.Id;
+import org.springframework.data.annotation.PersistenceConstructor;
/**
* @author Christoph Strobl
+ * @author Mark Paluch
*/
@Value
-public class PersonAggregate {
+class PersonAggregate {
@Id private String lastname;
private List names;
+
+ @PersistenceConstructor
+ public PersonAggregate(String lastname, List names) {
+ this.lastname = lastname;
+ this.names = names;
+ }
+
+ public PersonAggregate(String lastname, String name) {
+ this(lastname, Collections.singletonList(name));
+ }
}
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 59437cfbf..19b445ec5 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
@@ -381,7 +381,7 @@ public interface PersonRepository extends MongoRepository, Query
List groupByLastnameAnd(String property, Pageable page);
@Aggregation(pipeline = "{ '$group' : { '_id' : null, 'total' : { $sum: '$age' } } }")
- Long sumAge();
+ int sumAge();
@Aggregation(pipeline = "{ '$group' : { '_id' : null, 'total' : { $sum: '$age' } } }")
AggregationResults sumAgeAndReturnAggregationResultWrapper();
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 2007f3cee..55406f5eb 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,7 +15,7 @@
*/
package org.springframework.data.mongodb.repository;
-import static org.assertj.core.api.Assertions.offset;
+import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.domain.Sort.Direction.*;
import static org.springframework.data.mongodb.test.util.Assertions.assertThat;
@@ -27,7 +27,6 @@ import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.util.Arrays;
-import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
@@ -37,6 +36,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.reactivestreams.Publisher;
+
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -455,11 +455,11 @@ public class ReactiveMongoRepositoryTests {
.as(StepVerifier::create) //
.assertNext(actual -> {
assertThat(actual) //
- .contains(new PersonAggregate("Lessard", Collections.singletonList("Stefan"))) //
- .contains(new PersonAggregate("Keys", Collections.singletonList("Alicia"))) //
- .contains(new PersonAggregate("Tinsley", Collections.singletonList("Boyd"))) //
- .contains(new PersonAggregate("Beauford", Collections.singletonList("Carter"))) //
- .contains(new PersonAggregate("Moore", Collections.singletonList("Leroi"))) //
+ .contains(new PersonAggregate("Lessard", "Stefan")) //
+ .contains(new PersonAggregate("Keys", "Alicia")) //
+ .contains(new PersonAggregate("Tinsley", "Boyd")) //
+ .contains(new PersonAggregate("Beauford", "Carter")) //
+ .contains(new PersonAggregate("Moore", "Leroi")) //
.contains(new PersonAggregate("Matthews", Arrays.asList("Dave", "Oliver August")));
}).verifyComplete();
}
@@ -473,12 +473,12 @@ public class ReactiveMongoRepositoryTests {
.assertNext(actual -> {
assertThat(actual) //
.containsSequence( //
- new PersonAggregate("Beauford", Collections.singletonList("Carter")), //
- new PersonAggregate("Keys", Collections.singletonList("Alicia")), //
- new PersonAggregate("Lessard", Collections.singletonList("Stefan")), //
+ new PersonAggregate("Beauford", "Carter"), //
+ new PersonAggregate("Keys", "Alicia"), //
+ new PersonAggregate("Lessard", "Stefan"), //
new PersonAggregate("Matthews", Arrays.asList("Dave", "Oliver August")), //
- new PersonAggregate("Moore", Collections.singletonList("Leroi")), //
- new PersonAggregate("Tinsley", Collections.singletonList("Boyd")));
+ new PersonAggregate("Moore", "Leroi"), //
+ new PersonAggregate("Tinsley", "Boyd"));
}) //
.verifyComplete();
}
@@ -492,7 +492,7 @@ public class ReactiveMongoRepositoryTests {
.assertNext(actual -> {
assertThat(actual) //
.containsExactly( //
- new PersonAggregate("Lessard", Collections.singletonList("Stefan")), //
+ new PersonAggregate("Lessard", "Stefan"), //
new PersonAggregate("Matthews", Arrays.asList("Dave", "Oliver August")));
}) //
.verifyComplete();
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SumAge.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SumAge.java
index e0b99244d..ce4b41d81 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SumAge.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/SumAge.java
@@ -21,7 +21,7 @@ import lombok.Value;
* @author Christoph Strobl
*/
@Value
-public class SumAge {
+class SumAge {
private Long total;
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java
index cb9a4de1d..23735660e 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedAggregationUnitTests.java
@@ -16,7 +16,7 @@
package org.springframework.data.mongodb.repository.query;
import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import lombok.Value;
@@ -34,6 +34,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
@@ -55,8 +56,11 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
/**
+ * Unit tests for {@link ReactiveStringBasedAggregation}.
+ *
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
@@ -158,18 +162,12 @@ public class ReactiveStringBasedAggregationUnitTests {
private ReactiveStringBasedAggregation createAggregationForMethod(String name, Class>... parameters) {
- try {
-
- Method method = SampleRepository.class.getMethod(name, parameters);
- ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
- ReactiveMongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method,
- new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext());
- return new ReactiveStringBasedAggregation(queryMethod, operations, PARSER,
- QueryMethodEvaluationContextProvider.DEFAULT);
-
- } catch (Exception e) {
- throw new IllegalArgumentException(e.getMessage(), e);
- }
+ Method method = ClassUtils.getMethod(SampleRepository.class, name, parameters);
+ ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
+ ReactiveMongoQueryMethod queryMethod = new ReactiveMongoQueryMethod(method,
+ new DefaultRepositoryMetadata(SampleRepository.class), factory, converter.getMappingContext());
+ return new ReactiveStringBasedAggregation(queryMethod, operations, PARSER,
+ QueryMethodEvaluationContextProvider.DEFAULT);
}
private List pipelineOf(AggregationInvocation invocation) {
@@ -222,8 +220,8 @@ public class ReactiveStringBasedAggregationUnitTests {
@Value
static class AggregationInvocation {
- final TypedAggregation> aggregation;
- final Class> targetType;
- final Object result;
+ TypedAggregation> aggregation;
+ Class> targetType;
+ Object result;
}
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java
index e25d91854..44f718648 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedAggregationUnitTests.java
@@ -16,7 +16,7 @@
package org.springframework.data.mongodb.repository.query;
import static org.assertj.core.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import lombok.Value;
@@ -25,6 +25,7 @@ import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.function.IntFunction;
import org.bson.Document;
import org.junit.Before;
@@ -33,6 +34,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoOperations;
@@ -55,8 +57,11 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
/**
+ * Unit tests for {@link StringBasedAggregation}.
+ *
* @author Christoph Strobl
*/
@RunWith(MockitoJUnitRunner.class)
@@ -176,7 +181,7 @@ public class StringBasedAggregationUnitTests {
private AggregationInvocation executeAggregation(String name, Object... args) {
- Class>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(size -> new Class>[size]);
+ Class>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
StringBasedAggregation aggregation = createAggregationForMethod(name, argTypes);
ArgumentCaptor aggregationCaptor = ArgumentCaptor.forClass(TypedAggregation.class);
@@ -191,17 +196,11 @@ public class StringBasedAggregationUnitTests {
private StringBasedAggregation createAggregationForMethod(String name, Class>... parameters) {
- try {
-
- Method method = SampleRepository.class.getMethod(name, parameters);
- ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
- MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
- factory, converter.getMappingContext());
- return new StringBasedAggregation(queryMethod, operations, PARSER, QueryMethodEvaluationContextProvider.DEFAULT);
-
- } catch (Exception e) {
- throw new IllegalArgumentException(e.getMessage(), e);
- }
+ Method method = ClassUtils.getMethod(SampleRepository.class, name, parameters);
+ ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
+ MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
+ factory, converter.getMappingContext());
+ return new StringBasedAggregation(queryMethod, operations, PARSER, QueryMethodEvaluationContextProvider.DEFAULT);
}
private List pipelineOf(AggregationInvocation invocation) {
@@ -266,8 +265,8 @@ public class StringBasedAggregationUnitTests {
@Value
static class AggregationInvocation {
- final TypedAggregation> aggregation;
- final Class> targetType;
- final Object result;
+ TypedAggregation> aggregation;
+ Class> targetType;
+ Object result;
}
}
diff --git a/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc b/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc
index 70e5e295c..feba576ad 100644
--- a/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc
+++ b/src/main/asciidoc/reference/mongo-repositories-aggregation.adoc
@@ -1,11 +1,9 @@
[[mongodb.repositories.queries.aggregation]]
=== Aggregation Repository Methods
-The repository layer offers means interact with <> via annotated repository
-finder methods. Similar to the <> a pipeline can be defined
-via the `org.springframework.data.mongodb.repository.Aggregation` annotation. The definition may contain simple placeholders
-like `?0` as well as https://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#expressions[SpEL expressions]
-`?#{ ... }`.
+The repository layer offers means to interact with <> via annotated repository query methods.
+Similar to the <>, you can define a pipeline using the `org.springframework.data.mongodb.repository.Aggregation` annotation.
+The definition may contain simple placeholders like `?0` as well as https://docs.spring.io/spring/docs/{springVersion}/spring-framework-reference/core.html#expressions[SpEL expressions] `?#{ … }`.
.Aggregating Repository Method
====
@@ -13,33 +11,37 @@ like `?0` as well as https://docs.spring.io/spring/docs/{springVersion}/spring-f
----
public interface PersonRepository extends CrudReppsitory {
- @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }")
- List groupByLastnameAnd(String property); <1>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
- List groupByLastnameAndFirstnames(Sort sort); <2>
+ List groupByLastnameAndFirstnames(); <1>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
+ List groupByLastnameAndFirstnames(Sort sort); <2>
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }")
- List groupByLastnameAnd(String property, Pageable page); <3>
+ List groupByLastnameAnd(String property); <3>
+
+ @Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $?0 } } }")
+ List groupByLastnameAnd(String property, Pageable page); <4>
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- SumValue sumAgeUsingValueWrapper(); <4>
+ SumValue sumAgeUsingValueWrapper(); <5>
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- Long sumAge(); <5>
+ Long sumAge(); <6>
@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
- AggregationResults sumAgeRaw(); <6>
+ AggregationResults sumAgeRaw(); <7>
@Aggregation("{ '$project': { '_id' : '$lastname' } }")
- List findAllLastnames(); <7>
+ List findAllLastnames(); <8>
}
----
[source,java]
----
public class PersonAggregate {
- private @Id String lastname; <2>
+ private @Id String lastname; <2>
private List names;
public PersonAggregate(String lastname, List names) {
@@ -51,7 +53,7 @@ public class PersonAggregate {
public class SumValue {
- private final Long total; <4> <6>
+ private final Long total; <5> <7>
public SumValue(Long total) {
// ...
@@ -60,30 +62,28 @@ public class SumValue {
// Getter omitted
}
----
-<1> Replace `?0` with the given value for `property`.
-<2> If `Sort` argument is present, `$sort` is added at the pipelines tail so that it only affects the order of the final results
-after having passed all other aggregation stages. Therefore the `Sort` properties are mapped against the methods return type
-`PersonAggregate` which turns `Sort.by("lastname")` into `{ $sort : { '_id', 1 } }` because `PersonAggregate.lastname` is
-annotated with `@Id`.
-<3> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in 2., the operators are applied at
-the pipelines tail.
-<4> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
-<5> Aggregations resulting in single document holding just an accumulation result like eg. `$sum` can be extracted directly from
-the result `Document`. To gain more control one might consider `AggregationResult` as the methods return type as shown in 4. or 6.
-<6> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
-<7> Like in (5) a single value can be directly obtained from mutliple result ``Document``s.
+<1> Aggregation pipeline to group first names by `lastname` in the `Person` collection returning these as `PersonAggregate`.
+<2> If `Sort` argument is present, `$sort` is appended after the declared pipeline stages so that it only affects the order of the final results after having passed all other aggregation stages.
+Therefore, the `Sort` properties are mapped against the methods return type `PersonAggregate` which turns `Sort.by("lastname")` into `{ $sort : { '_id', 1 } }` because `PersonAggregate.lastname` is annotated with `@Id`.
+<3> Replaces `?0` with the given value for `property` for a dynamic aggregation pipeline.
+<4> `$skip`, `$limit` and `$sort` can be passed on via a `Pageable` argument. Same as in <2>, the operators are appended to the pipeline definition.
+<5> Map the result of an aggregation returning a single `Document` to an instance of a desired `SumValue` target type.
+<6> Aggregations resulting in single document holding just an accumulation result like eg. `$sum` can be extracted directly from the result `Document`.
+To gain more control, you might consider `AggregationResult` as method return type as shown in <7>.
+<7> Obtain the raw `AggregationResults` mapped to the generic target wrapper type `SumValue` or `org.bson.Document`.
+<8> Like in <6>, a single value can be directly obtained from multiple result ``Document``s.
====
-TIP: `@Aggregation` can also be used with <>.
+TIP: You can use `@Aggregation` also with <>.
[NOTE]
====
-Obtaining simple type single results inspects the returned `Document` and checks for the following
+Simple-type single-result inspects the returned `Document` and checks for the following:
. Only one entry in the document, return it.
. Two entries, one is the `_id` value. Return the other.
. Return for the first value assignable to the return type.
-. Throw an execption if none of the above applied.
+. Throw an exception if none of the above is applicable.
====
WARNING: The `Page` return type is not supported for repository methods using `@Aggregation`. However you can use a