Add support for modifying documents via repository method.

We now support findAndModify operations on derived query methods.

Closes: #2107
Original Pull Request: #284
This commit is contained in:
Thomas Darimont
2015-03-20 17:20:09 +01:00
committed by Christoph Strobl
parent 1c6c703640
commit 28708ce24e
10 changed files with 156 additions and 3 deletions

View File

@@ -17,12 +17,14 @@ package org.springframework.data.mongodb.repository.query;
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;
@@ -137,6 +139,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
} 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());
@@ -147,6 +154,10 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
} else {
return q -> {
if (method.isModifyingQuery()) {
return new UpdatingSingleEntityExecution(accessor.getUpdate()).execute(q);
}
TerminatingFind<?> find = operation.matching(q);
return isLimiting() ? find.firstValue() : find.oneValue();
};
@@ -267,4 +278,53 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
* @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());
}
}
}

View File

@@ -31,6 +31,7 @@ import org.springframework.data.mongodb.core.convert.MongoWriter;
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;
@@ -225,4 +226,11 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
Object nextConverted(MongoPersistentProperty property);
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getUpdate()
*/
@Override
public Update getUpdate() {
return delegate.getUpdate();
}
}

View File

@@ -20,6 +20,7 @@ import org.springframework.data.geo.Distance;
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;
@@ -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();
}

View File

@@ -25,6 +25,7 @@ import org.springframework.data.geo.Distance;
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;
@@ -39,6 +40,7 @@ import org.springframework.lang.Nullable;
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
* @author Thomas Darimont
*/
public class MongoParameters extends Parameters<MongoParameters, MongoParameter> {
@@ -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}.
@@ -67,6 +70,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
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) {
@@ -77,7 +81,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
}
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);
@@ -86,6 +90,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
this.maxDistanceIndex = maxDistanceIndex;
this.rangeIndex = rangeIndex;
this.collationIndex = collationIndex;
this.updateIndex = updateIndex;
}
private final int getNearIndex(List<Class<?>> parameterTypes) {
@@ -194,7 +199,7 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
@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) {
@@ -261,7 +266,9 @@ public class MongoParameters extends Parameters<MongoParameters, MongoParameter>
private boolean hasNearAnnotation() {
return parameter.getParameterAnnotation(Near.class) != null;
}
}
public int getUpdateIndex() {
return updateIndex;
}
}

View File

@@ -22,6 +22,7 @@ import org.springframework.data.geo.Point;
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;
@@ -137,4 +138,11 @@ public class MongoParametersParameterAccessor extends ParametersParameterAccesso
public Object[] getValues() {
return super.getValues();
}
@Override
public Update getUpdate() {
int updateIndex = method.getParameters().getUpdateIndex();
return updateIndex == -1 ? null : (Update) getValue(updateIndex);
}
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.geo.GeoResults;
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;
@@ -382,4 +383,11 @@ public class MongoQueryMethod extends QueryMethod {
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;
}
}

View File

@@ -1473,4 +1473,36 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
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);
}
}

View File

@@ -77,6 +77,8 @@ public class Person extends Contact {
@DocumentReference User spiritAnimal;
int visits;
public Person() {
this(null, null);
@@ -264,6 +266,14 @@ public class Person extends Contact {
this.coworker = coworker;
}
public int getVisits() {
return visits;
}
public void setVisits(int visits) {
this.visits = visits;
}
@Override
public boolean equals(Object obj) {

View File

@@ -36,6 +36,7 @@ import org.springframework.data.geo.GeoResults;
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;
@@ -419,6 +420,10 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
List<Person> findByUnwrappedUser(User user);
List<Person> findAndModifyByFirstname(String firstname, Update update);
Person findOneAndModifyByFirstname(String firstname, Update update);
@Query("{ 'age' : null }")
Person findByQueryWithNullEqualityCheck();

View File

@@ -28,6 +28,7 @@ import org.springframework.data.geo.Point;
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;
@@ -123,4 +124,9 @@ class StubParameterAccessor implements MongoParameterAccessor {
public Class<?> findDynamicProjection() {
return null;
}
@Override
public Update getUpdate() {
return null;
}
}