Skip to content

Commit 28708ce

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 1c6c703 commit 28708ce

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;
@@ -137,6 +139,11 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
137139
} else if (method.isStreamQuery()) {
138140
return q -> operation.matching(q).stream();
139141
} else if (method.isCollectionQuery()) {
142+
143+
if (method.isModifyingQuery()) {
144+
return q -> new UpdatingCollectionExecution(accessor.getPageable(), accessor.getUpdate()).execute(q);
145+
}
146+
140147
return q -> operation.matching(q.with(accessor.getPageable()).with(accessor.getSort())).all();
141148
} else if (method.isPageQuery()) {
142149
return new PagedExecution(operation, accessor.getPageable());
@@ -147,6 +154,10 @@ private MongoQueryExecution getExecution(ConvertingParameterAccessor accessor, F
147154
} else {
148155
return q -> {
149156

157+
if (method.isModifyingQuery()) {
158+
return new UpdatingSingleEntityExecution(accessor.getUpdate()).execute(q);
159+
}
160+
150161
TerminatingFind<?> find = operation.matching(q);
151162
return isLimiting() ? find.firstValue() : find.oneValue();
152163
};
@@ -267,4 +278,53 @@ protected CodecRegistry getCodecRegistry() {
267278
* @since 2.0.4
268279
*/
269280
protected abstract boolean isLimiting();
281+
282+
/**
283+
* {@link MongoQueryExecution} for collection returning find and update queries.
284+
*
285+
* @author Thomas Darimont
286+
*/
287+
final class UpdatingCollectionExecution implements MongoQueryExecution {
288+
289+
private final Pageable pageable;
290+
private final Update update;
291+
292+
UpdatingCollectionExecution(Pageable pageable, Update update) {
293+
this.pageable = pageable;
294+
this.update = update;
295+
}
296+
297+
@Override
298+
public Object execute(Query query) {
299+
300+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
301+
return operations.findAndModify(query.with(pageable), update, metadata.getJavaType(),
302+
metadata.getCollectionName());
303+
}
304+
}
305+
306+
/**
307+
* {@link MongoQueryExecution} to return a single entity with update.
308+
*
309+
* @author Thomas Darimont
310+
*/
311+
final class UpdatingSingleEntityExecution implements MongoQueryExecution {
312+
313+
private final Update update;
314+
315+
private UpdatingSingleEntityExecution(Update update) {
316+
this.update = update;
317+
}
318+
319+
/*
320+
* (non-Javadoc)
321+
* @see org.springframework.data.mongodb.repository.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.core.query.Query)
322+
*/
323+
@Override
324+
public Object execute(Query query) {
325+
326+
MongoEntityMetadata<?> metadata = method.getEntityInformation();
327+
return operations.findAndModify(query.limit(1), update, metadata.getJavaType(), metadata.getCollectionName());
328+
}
329+
}
270330
}

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;
@@ -225,4 +226,11 @@ public interface PotentiallyConvertingIterator extends Iterator<Object> {
225226
Object nextConverted(MongoPersistentProperty property);
226227
}
227228

229+
/* (non-Javadoc)
230+
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate()
231+
*/
232+
@Override
233+
public Update getUpdate() {
234+
return delegate.getUpdate();
235+
}
228236
}

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) {
@@ -194,7 +199,7 @@ public int getCollationParameterIndex() {
194199
@Override
195200
protected MongoParameters createFrom(List<MongoParameter> parameters) {
196201
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex,
197-
this.collationIndex);
202+
this.collationIndex, this.updateIndex);
198203
}
199204

200205
private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) {
@@ -261,7 +266,9 @@ private boolean isPoint() {
261266
private boolean hasNearAnnotation() {
262267
return parameter.getParameterAnnotation(Near.class) != null;
263268
}
264-
265269
}
266270

271+
public int getUpdateIndex() {
272+
return updateIndex;
273+
}
267274
}

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;
@@ -137,4 +138,11 @@ public Collation getCollation() {
137138
public Object[] getValues() {
138139
return super.getValues();
139140
}
141+
142+
@Override
143+
public Update getUpdate() {
144+
145+
int updateIndex = method.getParameters().getUpdateIndex();
146+
return updateIndex == -1 ? null : (Update) getValue(updateIndex);
147+
}
140148
}

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;
@@ -382,4 +383,11 @@ private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationT
382383
return (Optional<A>) this.annotationCache.computeIfAbsent(annotationType,
383384
it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, it)));
384385
}
386+
387+
@Override
388+
public boolean isModifyingQuery() {
389+
390+
Class<?>[] parameterTypes = this.method.getParameterTypes();
391+
return parameterTypes.length > 0 && parameterTypes[parameterTypes.length - 1] == Update.class;
392+
}
385393
}

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
@@ -77,6 +77,8 @@ public enum Sex {
7777

7878
@DocumentReference User spiritAnimal;
7979

80+
int visits;
81+
8082
public Person() {
8183

8284
this(null, null);
@@ -264,6 +266,14 @@ public void setCoworker(User coworker) {
264266
this.coworker = coworker;
265267
}
266268

269+
public int getVisits() {
270+
return visits;
271+
}
272+
273+
public void setVisits(int visits) {
274+
this.visits = visits;
275+
}
276+
267277
@Override
268278
public boolean equals(Object obj) {
269279

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

@@ -123,4 +124,9 @@ public Object[] getValues() {
123124
public Class<?> findDynamicProjection() {
124125
return null;
125126
}
127+
128+
@Override
129+
public Update getUpdate() {
130+
return null;
131+
}
126132
}

0 commit comments

Comments
 (0)