Skip to content

Commit

Permalink
Add support for modifying documents via repository method.
Browse files Browse the repository at this point in the history
We now support findAndModify operations on derived query methods.

Closes: #2107
Original Pull Request: #284
  • Loading branch information
Thomas Darimont authored and christophstrobl committed Feb 17, 2022
1 parent 1c6c703 commit 28708ce
Show file tree
Hide file tree
Showing 10 changed files with 156 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand All @@ -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();
};
Expand Down Expand Up @@ -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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -225,4 +226,11 @@ public interface PotentiallyConvertingIterator extends Iterator<Object> {
Object nextConverted(MongoPersistentProperty property);
}

/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate()
*/
@Override
public Update getUpdate() {
return delegate.getUpdate();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,6 +40,7 @@
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @author Thomas Darimont
*/
public class MongoParameters extends Parameters<MongoParameters, MongoParameter> {

Expand All @@ -47,6 +49,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
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}.
Expand All @@ -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) {
Expand All @@ -77,7 +81,7 @@ public MongoParameters(Method method, boolean isGeoNearMethod) {
}

private MongoParameters(List<MongoParameter> 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);

Expand All @@ -86,6 +90,7 @@ private MongoParameters(List<MongoParameter> parameters, int maxDistanceIndex, @
this.maxDistanceIndex = maxDistanceIndex;
this.rangeIndex = rangeIndex;
this.collationIndex = collationIndex;
this.updateIndex = updateIndex;
}

private final int getNearIndex(List<Class<?>> parameterTypes) {
Expand Down Expand Up @@ -194,7 +199,7 @@ public int getCollationParameterIndex() {
@Override
protected MongoParameters createFrom(List<MongoParameter> parameters) {
return new MongoParameters(parameters, this.maxDistanceIndex, this.nearIndex, this.fullTextIndex, this.rangeIndex,
this.collationIndex);
this.collationIndex, this.updateIndex);
}

private int getTypeIndex(List<TypeInformation<?>> parameterTypes, Class<?> type, @Nullable Class<?> componentType) {
Expand Down Expand Up @@ -261,7 +266,9 @@ private boolean isPoint() {
private boolean hasNearAnnotation() {
return parameter.getParameterAnnotation(Near.class) != null;
}

}

public int getUpdateIndex() {
return updateIndex;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -382,4 +383,11 @@ private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationT
return (Optional<A>) 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1473,4 +1473,36 @@ void resultProjectionWithOptionalIsExcecutedCorrectly() {
assertThat(result.getAddress()).isPresent();
assertThat(result.getFirstname()).contains("Carter");
}

/**
* @see DATAMONGO-1188
*/
@Test
public void shouldSupportFindAndModfiyForQueryDerivationWithCollectionResult() {

List<Person> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public enum Sex {

@DocumentReference User spiritAnimal;

int visits;

public Person() {

this(null, null);
Expand Down Expand Up @@ -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) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -419,6 +420,10 @@ Person findPersonByManyArguments(String firstname, String lastname, String email

List<Person> findByUnwrappedUser(User user);

List<Person> findAndModifyByFirstname(String firstname, Update update);

Person findOneAndModifyByFirstname(String firstname, Update update);

@Query("{ 'age' : null }")
Person findByQueryWithNullEqualityCheck();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -123,4 +124,9 @@ public Object[] getValues() {
public Class<?> findDynamicProjection() {
return null;
}

@Override
public Update getUpdate() {
return null;
}
}

0 comments on commit 28708ce

Please sign in to comment.