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:
committed by
Christoph Strobl
parent
1c6c703640
commit
28708ce24e
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user