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 f768cf545..46cd175d3 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 @@ -17,12 +17,14 @@ package org.springframework.data.mongodb.repository.query; import org.bson.Document; import org.bson.codecs.configuration.CodecRegistry; +import org.springframework.data.domain.Pageable; import org.springframework.data.mapping.model.SpELExpressionEvaluator; import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution; @@ -145,6 +147,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { } else if (method.isStreamQuery()) { return q -> operation.matching(q).stream(); } else if (method.isCollectionQuery()) { + + if (method.isModifyingQuery()) { + return q -> new UpdatingCollectionExecution(accessor.getPageable(), accessor.getUpdate()).execute(q); + } + return q -> operation.matching(q.with(accessor.getPageable()).with(accessor.getSort())).all(); } else if (method.isPageQuery()) { return new PagedExecution(operation, accessor.getPageable()); @@ -155,6 +162,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { } else { return q -> { + if (method.isModifyingQuery()) { + return new UpdatingSingleEntityExecution(accessor.getUpdate()).execute(q); + } + TerminatingFind find = operation.matching(q); return isLimiting() ? find.firstValue() : find.oneValue(); }; @@ -275,4 +286,53 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { * @since 2.0.4 */ protected abstract boolean isLimiting(); + + /** + * {@link MongoQueryExecution} for collection returning find and update queries. + * + * @author Thomas Darimont + */ + final class UpdatingCollectionExecution implements MongoQueryExecution { + + private final Pageable pageable; + private final Update update; + + UpdatingCollectionExecution(Pageable pageable, Update update) { + this.pageable = pageable; + this.update = update; + } + + @Override + public Object execute(Query query) { + + MongoEntityMetadata metadata = method.getEntityInformation(); + return operations.findAndModify(query.with(pageable), update, metadata.getJavaType(), + metadata.getCollectionName()); + } + } + + /** + * {@link MongoQueryExecution} to return a single entity with update. + * + * @author Thomas Darimont + */ + final class UpdatingSingleEntityExecution implements MongoQueryExecution { + + private final Update update; + + private UpdatingSingleEntityExecution(Update update) { + this.update = update; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query) + */ + @Override + public Object execute(Query query) { + + MongoEntityMetadata metadata = method.getEntityInformation(); + return operations.findAndModify(query.limit(1), update, metadata.getJavaType(), metadata.getCollectionName()); + } + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java index a880a1c05..91d2efcb3 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java @@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; @@ -297,4 +298,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor { Object nextConverted(MongoPersistentProperty property); } + /* (non-Javadoc) + * @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate() + */ + @Override + public Update getUpdate() { + return delegate.getUpdate(); + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java index a934d6fcc..6b42fdafd 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java @@ -20,6 +20,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.lang.Nullable; @@ -74,4 +75,12 @@ public interface MongoParameterAccessor extends ParameterAccessor { * @since 1.8 */ Object[] getValues(); + + /** + * Returns the {@link Update} to be used for findAndUpdate query. + * + * @return + * @since 1.7 + */ + Update getUpdate(); } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java index 1e2ac33f1..5a54e587d 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java @@ -25,6 +25,7 @@ import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.repository.Near; import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter; import org.springframework.data.repository.query.Parameter; @@ -39,6 +40,7 @@ import org.springframework.lang.Nullable; * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch + * @author Thomas Darimont */ public class MongoParameters extends Parameters { @@ -47,6 +49,7 @@ public class MongoParameters extends Parameters private final @Nullable Integer fullTextIndex; private final @Nullable Integer nearIndex; private final @Nullable Integer collationIndex; + private final int updateIndex; /** * Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}. @@ -67,6 +70,7 @@ public class MongoParameters extends Parameters this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class); this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1; this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null); + this.updateIndex = parameterTypes.indexOf(Update.class); int index = findNearIndexInParameters(method); if (index == -1 && isGeoNearMethod) { @@ -77,7 +81,7 @@ public class MongoParameters extends Parameters } private MongoParameters(List parameters, int maxDistanceIndex, @Nullable Integer nearIndex, - @Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex) { + @Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex, int updateIndex) { super(parameters); @@ -86,6 +90,7 @@ public class MongoParameters extends Parameters this.maxDistanceIndex = maxDistanceIndex; this.rangeIndex = rangeIndex; this.collationIndex = collationIndex; + this.updateIndex = updateIndex; } private final int getNearIndex(List> parameterTypes) { @@ -202,7 +207,7 @@ public class MongoParameters extends Parameters @Override protected MongoParameters createFrom(List parameters) { return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex, - this.collationIndex); + this.collationIndex, this.updateIndex); } private int getTypeIndex(List> parameterTypes, Class type, @Nullable Class componentType) { @@ -273,7 +278,9 @@ public class MongoParameters extends Parameters private boolean hasNearAnnotation() { return parameter.getParameterAnnotation(Near.class) != null; } - } + public int getUpdateIndex() { + return updateIndex; + } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java index a1b6a8f2e..a21762688 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java @@ -22,6 +22,7 @@ import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Term; import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.repository.query.ParametersParameterAccessor; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -153,4 +154,11 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso public Object[] getValues() { return super.getValues(); } + + @Override + public Update getUpdate() { + + int updateIndex = method.getParameters().getUpdateIndex(); + return updateIndex == -1 ? null : (Update) getValue(updateIndex); + } } 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 e97dae204..191bec49a 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 @@ -30,6 +30,7 @@ import org.springframework.data.geo.GeoResults; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.Meta; import org.springframework.data.mongodb.repository.Query; @@ -398,4 +399,11 @@ public class MongoQueryMethod extends QueryMethod { return (Optional) this.annotationCache.computeIfAbsent(annotationType, it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it))); } + + @Override + public boolean isModifyingQuery() { + + Class[] parameterTypes = this.method.getParameterTypes(); + return parameterTypes.length > 0 && parameterTypes[parameterTypes.length - 1] == Update.class; + } } 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 e462458ae..658558d3d 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 @@ -1473,4 +1473,36 @@ public abstract class AbstractPersonRepositoryIntegrationTests { assertThat(result.getAddress()).isPresent(); assertThat(result.getFirstname()).contains("Carter"); } + + /** + * @see DATAMONGO-1188 + */ + @Test + public void shouldSupportFindAndModfiyForQueryDerivationWithCollectionResult() { + + List result = repository.findAndModifyByFirstname("Dave", new Update().inc("visits", 42)); + + assertThat(result.size()).isOne(); + assertThat(result.get(0)).isEqualTo(dave); + + Person dave = repository.findById(result.get(0).getId()).get(); + + assertThat(dave.visits).isEqualTo(42); + } + + /** + * @see DATAMONGO-1188 + */ + @Test + public void shouldSupportFindAndModfiyForQueryDerivationWithSingleResult() { + + Person result = repository.findOneAndModifyByFirstname("Dave", new Update().inc("visits", 1337)); + + assertThat(result).isEqualTo(dave); + + Person dave = repository.findById(result.getId()).get(); + + assertThat(dave.visits).isEqualTo(1337); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java index 62c5b18be..ac2437f10 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java @@ -78,6 +78,8 @@ public class Person extends Contact { @DocumentReference User spiritAnimal; + int visits; + public Person() { this(null, null); @@ -265,6 +267,14 @@ public class Person extends Contact { this.coworker = coworker; } + public int getVisits() { + return visits; + } + + public void setVisits(int visits) { + this.visits = visits; + } + /* * (non-Javadoc) * 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 9ac128208..b82bcb1c4 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 @@ -36,6 +36,7 @@ import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.aggregation.AggregationResults; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.mongodb.repository.Person.Sex; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.query.Param; @@ -419,6 +420,10 @@ public interface PersonRepository extends MongoRepository, Query List findByUnwrappedUser(User user); + List findAndModifyByFirstname(String firstname, Update update); + + Person findOneAndModifyByFirstname(String firstname, Update update); + @Query("{ 'age' : null }") Person findByQueryWithNullEqualityCheck(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java index f43e3a107..4c0ef87a6 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java @@ -28,6 +28,7 @@ import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.convert.MongoWriter; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.TextCriteria; +import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.lang.Nullable; @@ -172,4 +173,9 @@ class StubParameterAccessor implements MongoParameterAccessor { public Class findDynamicProjection() { return null; } + + @Override + public Update getUpdate() { + return null; + } }