Skip to content

Commit 6e3e821

Browse files
Thomas Darimontchristophstrobl
Thomas Darimont
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

+60
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

+8
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

+9
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

+10-3
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

+8
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

+8
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

+32
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

+10
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

+5
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

+6
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)