Skip to content

Commit 6e3e821

Browse files
Thomas Darimontchristophstrobl
authored andcommitted
Add support for modifying documents via repository method.
We now support findAndModify operations on derived query methods. Closes: #2107 Original Pull Request: #284
1 parent c071866 commit 6e3e821

File tree

10 files changed

+156
-3
lines changed

10 files changed

+156
-3
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717

1818
import org.bson.Document;
1919
import org.bson.codecs.configuration.CodecRegistry;
20+
import org.springframework.data.domain.Pageable;
2021
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
2122
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
2223
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
2324
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
2425
import org.springframework.data.mongodb.core.MongoOperations;
2526
import org.springframework.data.mongodb.core.query.Query;
27+
import org.springframework.data.mongodb.core.query.Update;
2628
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
2729
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution;
2830
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution;
@@ -145,6 +147,11 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
145147
} else if (method.isStreamQuery()) {
146148
return q -> operation.matching(q).stream();
147149
} else if (method.isCollectionQuery()) {
150+
151+
if (method.isModifyingQuery()) {
152+
return q -> new UpdatingCollectionExecution(accessor.getPageable(), accessor.getUpdate()).execute(q);
153+
}
154+
148155
return q -> operation.matching(q.with(accessor.getPageable()).with(accessor.getSort())).all();
149156
} else if (method.isPageQuery()) {
150157
return new PagedExecution(operation, accessor.getPageable());
@@ -155,6 +162,10 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
155162
} else {
156163
return q -> {
157164

165+
if (method.isModifyingQuery()) {
166+
return new UpdatingSingleEntityExecution(accessor.getUpdate()).execute(q);
167+
}
168+
158169
TerminatingFind<?> find = operation.matching(q);
159170
return isLimiting() ? find.firstValue() : find.oneValue();
160171
};
@@ -275,4 +286,53 @@ protected CodecRegistry getCodecRegistry() {
275286
* @since 2.0.4
276287
*/
277288
protected abstract boolean isLimiting();
289+
290+
/**
291+
* {@link MongoQueryExecution} for collection returning find and update queries.
292+
*
293+
* @author Thomas Darimont
294+
*/
295+
final class UpdatingCollectionExecution implements MongoQueryExecution {
296+
297+
private final Pageable pageable;
298+
private final Update update;
299+
300+
UpdatingCollectionExecution(Pageable pageable, Update update) {
301+
this.pageable = pageable;
302+
this.update = update;
303+
}
304+
305+
@Override
306+
public Object execute(Query query) {
307+
308+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
309+
return operations.findAndModify(query.with(pageable), update, metadata.getJavaType(),
310+
metadata.getCollectionName());
311+
}
312+
}
313+
314+
/**
315+
* {@link MongoQueryExecution} to return a single entity with update.
316+
*
317+
* @author Thomas Darimont
318+
*/
319+
final class UpdatingSingleEntityExecution implements MongoQueryExecution {
320+
321+
private final Update update;
322+
323+
private UpdatingSingleEntityExecution(Update update) {
324+
this.update = update;
325+
}
326+
327+
/*
328+
* (non-Javadoc)
329+
* @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query)
330+
*/
331+
@Override
332+
public Object execute(Query query) {
333+
334+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
335+
return operations.findAndModify(query.limit(1), update, metadata.getJavaType(), metadata.getCollectionName());
336+
}
337+
}
278338
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ConvertingParameterAccessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
3232
import org.springframework.data.mongodb.core.query.Collation;
3333
import org.springframework.data.mongodb.core.query.TextCriteria;
34+
import org.springframework.data.mongodb.core.query.Update;
3435
import org.springframework.data.repository.query.ParameterAccessor;
3536
import org.springframework.data.util.TypeInformation;
3637
import org.springframework.lang.Nullable;
@@ -297,4 +298,11 @@ public interface PotentiallyConvertingIterator extends Iterator<Object> {
297298
Object nextConverted(MongoPersistentProperty property);
298299
}
299300

301+
/* (non-Javadoc)
302+
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate()
303+
*/
304+
@Override
305+
public Update getUpdate() {
306+
return delegate.getUpdate();
307+
}
300308
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameterAccessor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.data.geo.Point;
2121
import org.springframework.data.mongodb.core.query.Collation;
2222
import org.springframework.data.mongodb.core.query.TextCriteria;
23+
import org.springframework.data.mongodb.core.query.Update;
2324
import org.springframework.data.repository.query.ParameterAccessor;
2425
import org.springframework.lang.Nullable;
2526

@@ -74,4 +75,12 @@ public interface MongoParameterAccessor extends ParameterAccessor {
7475
* @since 1.8
7576
*/
7677
Object[] getValues();
78+
79+
/**
80+
* Returns the {@link Update} to be used for findAndUpdate query.
81+
*
82+
* @return
83+
* @since 1.7
84+
*/
85+
Update getUpdate();
7786
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParameters.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.data.geo.Point;
2626
import org.springframework.data.mongodb.core.query.Collation;
2727
import org.springframework.data.mongodb.core.query.TextCriteria;
28+
import org.springframework.data.mongodb.core.query.Update;
2829
import org.springframework.data.mongodb.repository.Near;
2930
import org.springframework.data.mongodb.repository.query.MongoParameters.MongoParameter;
3031
import org.springframework.data.repository.query.Parameter;
@@ -39,6 +40,7 @@
3940
* @author Oliver Gierke
4041
* @author Christoph Strobl
4142
* @author Mark Paluch
43+
* @author Thomas Darimont
4244
*/
4345
public class MongoParameters extends Parameters<MongoParameters, MongoParameter> {
4446

@@ -47,6 +49,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
4749
private final @Nullable Integer fullTextIndex;
4850
private final @Nullable Integer nearIndex;
4951
private final @Nullable Integer collationIndex;
52+
private final int updateIndex;
5053

5154
/**
5255
* Creates a new {@link MongoParameters} instance from the given {@link Method} and {@link MongoQueryMethod}.
@@ -67,6 +70,7 @@ public MongoParameters(Method method, boolean isGeoNearMethod) {
6770
this.rangeIndex = getTypeIndex(parameterTypeInfo, Range.class, Distance.class);
6871
this.maxDistanceIndex = this.rangeIndex == -1 ? getTypeIndex(parameterTypeInfo, Distance.class, null) : -1;
6972
this.collationIndex = getTypeIndex(parameterTypeInfo, Collation.class, null);
73+
this.updateIndex = parameterTypes.indexOf(Update.class);
7074

7175
int index = findNearIndexInParameters(method);
7276
if (index == -1 && isGeoNearMethod) {
@@ -77,7 +81,7 @@ public MongoParameters(Method method, boolean isGeoNearMethod) {
7781
}
7882

7983
private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, @Nullable Integer nearIndex,
80-
@Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex) {
84+
@Nullable Integer fullTextIndex, int rangeIndex, @Nullable Integer collationIndex, int updateIndex) {
8185

8286
super(parameters);
8387

@@ -86,6 +90,7 @@ private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, @
8690
this.maxDistanceIndex = maxDistanceIndex;
8791
this.rangeIndex = rangeIndex;
8892
this.collationIndex = collationIndex;
93+
this.updateIndex = updateIndex;
8994
}
9095

9196
private final int getNearIndex(List<Class<?>> parameterTypes) {
@@ -202,7 +207,7 @@ public int getCollationParameterIndex() {
202207
@Override
203208
protected MongoParameters createFrom(List<MongoParameter> parameters) {
204209
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex,
205-
this.collationIndex);
210+
this.collationIndex, this.updateIndex);
206211
}
207212

208213
private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) {
@@ -273,7 +278,9 @@ private boolean isPoint() {
273278
private boolean hasNearAnnotation() {
274279
return parameter.getParameterAnnotation(Near.class) != null;
275280
}
276-
277281
}
278282

283+
public int getUpdateIndex() {
284+
return updateIndex;
285+
}
279286
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoParametersParameterAccessor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.data.mongodb.core.query.Collation;
2323
import org.springframework.data.mongodb.core.query.Term;
2424
import org.springframework.data.mongodb.core.query.TextCriteria;
25+
import org.springframework.data.mongodb.core.query.Update;
2526
import org.springframework.data.repository.query.ParametersParameterAccessor;
2627
import org.springframework.lang.Nullable;
2728
import org.springframework.util.Assert;
@@ -153,4 +154,11 @@ public Collation getCollation() {
153154
public Object[] getValues() {
154155
return super.getValues();
155156
}
157+
158+
@Override
159+
public Update getUpdate() {
160+
161+
int updateIndex = method.getParameters().getUpdateIndex();
162+
return updateIndex == -1 ? null : (Update) getValue(updateIndex);
163+
}
156164
}

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryMethod.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.data.mapping.context.MappingContext;
3131
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
3232
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
33+
import org.springframework.data.mongodb.core.query.Update;
3334
import org.springframework.data.mongodb.repository.Aggregation;
3435
import org.springframework.data.mongodb.repository.Meta;
3536
import org.springframework.data.mongodb.repository.Query;
@@ -398,4 +399,11 @@ private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationT
398399
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
399400
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
400401
}
402+
403+
@Override
404+
public boolean isModifyingQuery() {
405+
406+
Class<?>[] parameterTypes = this.method.getParameterTypes();
407+
return parameterTypes.length > 0 && parameterTypes[parameterTypes.length - 1] == Update.class;
408+
}
401409
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1473,4 +1473,36 @@ void resultProjectionWithOptionalIsExcecutedCorrectly() {
14731473
assertThat(result.getAddress()).isPresent();
14741474
assertThat(result.getFirstname()).contains("Carter");
14751475
}
1476+
1477+
/**
1478+
* @see DATAMONGO-1188
1479+
*/
1480+
@Test
1481+
public void shouldSupportFindAndModfiyForQueryDerivationWithCollectionResult() {
1482+
1483+
List<Person> result = repository.findAndModifyByFirstname("Dave", new Update().inc("visits", 42));
1484+
1485+
assertThat(result.size()).isOne();
1486+
assertThat(result.get(0)).isEqualTo(dave);
1487+
1488+
Person dave = repository.findById(result.get(0).getId()).get();
1489+
1490+
assertThat(dave.visits).isEqualTo(42);
1491+
}
1492+
1493+
/**
1494+
* @see DATAMONGO-1188
1495+
*/
1496+
@Test
1497+
public void shouldSupportFindAndModfiyForQueryDerivationWithSingleResult() {
1498+
1499+
Person result = repository.findOneAndModifyByFirstname("Dave", new Update().inc("visits", 1337));
1500+
1501+
assertThat(result).isEqualTo(dave);
1502+
1503+
Person dave = repository.findById(result.getId()).get();
1504+
1505+
assertThat(dave.visits).isEqualTo(1337);
1506+
}
1507+
14761508
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ public enum Sex {
7878
@DocumentReference
7979
User spiritAnimal;
8080

81+
int visits;
82+
8183
public Person() {
8284

8385
this(null, null);
@@ -265,6 +267,14 @@ public void setCoworker(User coworker) {
265267
this.coworker = coworker;
266268
}
267269

270+
public int getVisits() {
271+
return visits;
272+
}
273+
274+
public void setVisits(int visits) {
275+
this.visits = visits;
276+
}
277+
268278
/*
269279
* (non-Javadoc)
270280
*

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.data.geo.Point;
3737
import org.springframework.data.geo.Polygon;
3838
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
39+
import org.springframework.data.mongodb.core.query.Update;
3940
import org.springframework.data.mongodb.repository.Person.Sex;
4041
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
4142
import org.springframework.data.repository.query.Param;
@@ -419,6 +420,10 @@ Person findPersonByManyArguments(String firstname, String lastname, String email
419420

420421
List<Person> findByUnwrappedUser(User user);
421422

423+
List<Person> findAndModifyByFirstname(String firstname, Update update);
424+
425+
Person findOneAndModifyByFirstname(String firstname, Update update);
426+
422427
@Query("{ 'age' : null }")
423428
Person findByQueryWithNullEqualityCheck();
424429

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StubParameterAccessor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.mongodb.core.convert.MongoWriter;
2929
import org.springframework.data.mongodb.core.query.Collation;
3030
import org.springframework.data.mongodb.core.query.TextCriteria;
31+
import org.springframework.data.mongodb.core.query.Update;
3132
import org.springframework.data.repository.query.ParameterAccessor;
3233
import org.springframework.lang.Nullable;
3334

@@ -172,4 +173,9 @@ public Optional<Class<?>> getDynamicProjection() {
172173
public Class<?> findDynamicProjection() {
173174
return null;
174175
}
176+
177+
@Override
178+
public Update getUpdate() {
179+
return null;
180+
}
175181
}

0 commit comments

Comments
 (0)