Skip to content

Commit 28708ce

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

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;
@@ -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

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;
@@ -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

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) {
@@ -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

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;
@@ -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

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;
@@ -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

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
@@ -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

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

@@ -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)