From 28708ce24ec352d82ac125197f07726730cfa652 Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Fri, 20 Mar 2015 17:20:09 +0100 Subject: [PATCH] Add support for modifying documents via repository method. We now support findAndModify operations on derived query methods. Closes: #2107 Original Pull Request: #284 --- .../repository/query/AbstractMongoQuery.java | 60 +++++++++++++++++++ .../query/ConvertingParameterAccessor.java | 8 +++ .../query/MongoParameterAccessor.java | 9 +++ .../repository/query/MongoParameters.java | 13 +++- .../MongoParametersParameterAccessor.java | 8 +++ .../repository/query/MongoQueryMethod.java | 8 +++ ...tractPersonRepositoryIntegrationTests.java | 32 ++++++++++ .../data/mongodb/repository/Person.java | 10 ++++ .../mongodb/repository/PersonRepository.java | 5 ++ .../query/StubParameterAccessor.java | 6 ++ 10 files changed, 156 insertions(+), 3 deletions(-) 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 9b2d7d0688..6e95c79787 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 @@ 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; @@ -137,6 +139,11 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F } 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()); @@ -147,6 +154,10 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F } else { return q -> { + if (method.isModifyingQuery()) { + return new UpdatingSingleEntityExecution(accessor.getUpdate()).execute(q); + } + TerminatingFind find = operation.matching(q); return isLimiting() ? find.firstValue() : find.oneValue(); }; @@ -267,4 +278,53 @@ protected CodecRegistry getCodecRegistry() { * @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 2b02a92cf9..48cfacec93 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.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; @@ -225,4 +226,11 @@ public interface PotentiallyConvertingIterator extends Iterator { 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 a934d6fcc3..6b42fdafd5 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.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 7421dbbddf..66ffa28065 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.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 @@ * @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 MongoParameters(Method method, boolean isGeoNearMethod) { 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 MongoParameters(Method method, boolean isGeoNearMethod) { } 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 @@ private MongoParameters(List parameters, int maxDistanceIndex, @ this.maxDistanceIndex = maxDistanceIndex; this.rangeIndex = rangeIndex; this.collationIndex = collationIndex; + this.updateIndex = updateIndex; } private final int getNearIndex(List> parameterTypes) { @@ -194,7 +199,7 @@ public int getCollationParameterIndex() { @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) { @@ -261,7 +266,9 @@ private boolean isPoint() { 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 e4ce73f7ff..8a00d12039 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.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; @@ -137,4 +138,11 @@ public Collation getCollation() { 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 e364b2c167..03285e027f 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.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; @@ -382,4 +383,11 @@ private Optional doFindAnnotation(Class annotationT 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 e462458ae8..658558d3d4 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 @@ void resultProjectionWithOptionalIsExcecutedCorrectly() { 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 dd2c3ea50a..00d565deb5 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 @@ -77,6 +77,8 @@ public enum Sex { @DocumentReference User spiritAnimal; + int visits; + public Person() { this(null, null); @@ -264,6 +266,14 @@ public void setCoworker(User coworker) { this.coworker = coworker; } + public int getVisits() { + return visits; + } + + public void setVisits(int visits) { + this.visits = visits; + } + @Override public boolean equals(Object obj) { 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 9ac1282088..b82bcb1c41 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.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 @@ Person findPersonByManyArguments(String firstname, String lastname, String email 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 79a49f2ac6..927e9af78e 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.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; @@ -123,4 +124,9 @@ public Object[] getValues() { public Class findDynamicProjection() { return null; } + + @Override + public Update getUpdate() { + return null; + } }