DATAMONGO-1865 - Avoid IncorrectResultSizeDataAccessException for derived findFirst/findTop queries.
We now return the first result when executing findFirst/findTop queries. This fixes a glitch introduced in the Kay release throwing IncorrectResultSizeDataAccessException for single entity executions returning more than one result, which is explicitly not the desired behavior in this case. Original pull request: #530.
This commit is contained in:
committed by
Mark Paluch
parent
6a20ddf5a2
commit
c668a47243
@@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query;
|
|||||||
|
|
||||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
import org.springframework.data.mongodb.core.ExecutableFindOperation.ExecutableFind;
|
||||||
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
|
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.MongoOperations;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
|
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution;
|
||||||
@@ -117,7 +118,11 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
} else if (isExistsQuery()) {
|
} else if (isExistsQuery()) {
|
||||||
return q -> operation.matching(q).exists();
|
return q -> operation.matching(q).exists();
|
||||||
} else {
|
} else {
|
||||||
return q -> operation.matching(q).oneValue();
|
return q -> {
|
||||||
|
|
||||||
|
TerminatingFind<?> find = operation.matching(q);
|
||||||
|
return isLimiting() ? find.firstValue() : find.oneValue();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,4 +177,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery {
|
|||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
protected abstract boolean isDeleteQuery();
|
protected abstract boolean isDeleteQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return weather the query has an explicit limit set.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @since 2.0.4
|
||||||
|
*/
|
||||||
|
protected abstract boolean isLimiting();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,20 @@ import org.reactivestreams.Publisher;
|
|||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.data.convert.EntityInstantiators;
|
import org.springframework.data.convert.EntityInstantiators;
|
||||||
import org.springframework.data.mongodb.core.MongoOperations;
|
import org.springframework.data.mongodb.core.MongoOperations;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithProjection;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveFindOperation.FindWithQuery;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveFindOperation.TerminatingFind;
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||||
import org.springframework.data.mongodb.core.query.Query;
|
import org.springframework.data.mongodb.core.query.Query;
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.CollectionExecution;
|
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.DeleteExecution;
|
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.DeleteExecution;
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.GeoNearExecution;
|
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.GeoNearExecution;
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingConverter;
|
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingConverter;
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingExecution;
|
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.ResultProcessingExecution;
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.SingleEntityExecution;
|
|
||||||
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.TailExecution;
|
import org.springframework.data.mongodb.repository.query.ReactiveMongoQueryExecution.TailExecution;
|
||||||
import org.springframework.data.repository.query.ParameterAccessor;
|
import org.springframework.data.repository.query.ParameterAccessor;
|
||||||
import org.springframework.data.repository.query.RepositoryQuery;
|
import org.springframework.data.repository.query.RepositoryQuery;
|
||||||
import org.springframework.data.repository.query.ResultProcessor;
|
import org.springframework.data.repository.query.ResultProcessor;
|
||||||
|
import org.springframework.data.repository.query.ReturnedType;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,6 +50,7 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
private final ReactiveMongoQueryMethod method;
|
private final ReactiveMongoQueryMethod method;
|
||||||
private final ReactiveMongoOperations operations;
|
private final ReactiveMongoOperations operations;
|
||||||
private final EntityInstantiators instantiators;
|
private final EntityInstantiators instantiators;
|
||||||
|
private final FindWithProjection<?> findOperationWithProjection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
|
* Creates a new {@link AbstractReactiveMongoQuery} from the given {@link MongoQueryMethod} and
|
||||||
@@ -64,6 +67,12 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
this.method = method;
|
this.method = method;
|
||||||
this.operations = operations;
|
this.operations = operations;
|
||||||
this.instantiators = new EntityInstantiators();
|
this.instantiators = new EntityInstantiators();
|
||||||
|
|
||||||
|
ReturnedType returnedType = method.getResultProcessor().getReturnedType();
|
||||||
|
|
||||||
|
this.findOperationWithProjection = operations//
|
||||||
|
.query(returnedType.getDomainType())//
|
||||||
|
.inCollection(method.getEntityInformation().getCollectionName());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -103,10 +112,16 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
applyQueryMetaAttributesWhenPresent(query);
|
applyQueryMetaAttributesWhenPresent(query);
|
||||||
|
|
||||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
|
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||||
|
Class<?> typeToRead = processor.getReturnedType().getTypeToRead();
|
||||||
|
|
||||||
|
FindWithQuery<?> find = typeToRead == null //
|
||||||
|
? findOperationWithProjection //
|
||||||
|
: findOperationWithProjection.as(typeToRead);
|
||||||
|
|
||||||
String collection = method.getEntityInformation().getCollectionName();
|
String collection = method.getEntityInformation().getCollectionName();
|
||||||
|
|
||||||
ReactiveMongoQueryExecution execution = getExecution(query, parameterAccessor,
|
ReactiveMongoQueryExecution execution = getExecution(query, parameterAccessor,
|
||||||
new ResultProcessingConverter(processor, operations, instantiators));
|
new ResultProcessingConverter(processor, operations, instantiators), find);
|
||||||
|
|
||||||
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
return execution.execute(query, processor.getReturnedType().getDomainType(), collection);
|
||||||
}
|
}
|
||||||
@@ -120,11 +135,11 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private ReactiveMongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor,
|
private ReactiveMongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor,
|
||||||
Converter<Object, Object> resultProcessing) {
|
Converter<Object, Object> resultProcessing, FindWithQuery<?> operation) {
|
||||||
return new ResultProcessingExecution(getExecutionToWrap(accessor), resultProcessing);
|
return new ResultProcessingExecution(getExecutionToWrap(accessor, operation), resultProcessing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReactiveMongoQueryExecution getExecutionToWrap(MongoParameterAccessor accessor) {
|
private ReactiveMongoQueryExecution getExecutionToWrap(MongoParameterAccessor accessor, FindWithQuery<?> operation) {
|
||||||
|
|
||||||
if (isDeleteQuery()) {
|
if (isDeleteQuery()) {
|
||||||
return new DeleteExecution(operations, method);
|
return new DeleteExecution(operations, method);
|
||||||
@@ -133,9 +148,20 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
} else if (isTailable(method)) {
|
} else if (isTailable(method)) {
|
||||||
return new TailExecution(operations, accessor.getPageable());
|
return new TailExecution(operations, accessor.getPageable());
|
||||||
} else if (method.isCollectionQuery()) {
|
} else if (method.isCollectionQuery()) {
|
||||||
return new CollectionExecution(operations, accessor.getPageable());
|
return (q, t, c) -> operation.matching(q.with(accessor.getPageable())).all();
|
||||||
|
} else if (isCountQuery()) {
|
||||||
|
return (q, t, c) -> operation.matching(q).count();
|
||||||
} else {
|
} else {
|
||||||
return new SingleEntityExecution(operations, isCountQuery());
|
return (q, t, c) -> {
|
||||||
|
|
||||||
|
TerminatingFind<?> find = operation.matching(q);
|
||||||
|
|
||||||
|
if (isCountQuery()) {
|
||||||
|
return find.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
return isLimiting() ? find.first() : find.one();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,4 +212,12 @@ public abstract class AbstractReactiveMongoQuery implements RepositoryQuery {
|
|||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
protected abstract boolean isDeleteQuery();
|
protected abstract boolean isDeleteQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return weather the query has an explicit limit set.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* @since 2.0.4
|
||||||
|
*/
|
||||||
|
protected abstract boolean isLimiting();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,4 +160,13 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
|
|||||||
protected boolean isDeleteQuery() {
|
protected boolean isDeleteQuery() {
|
||||||
return tree.isDelete();
|
return tree.isDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isLimiting() {
|
||||||
|
return tree.isLimiting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,29 +44,13 @@ import com.mongodb.client.result.DeleteResult;
|
|||||||
* various flavors.
|
* various flavors.
|
||||||
*
|
*
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
|
* @author Christoph Strobl
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
interface ReactiveMongoQueryExecution {
|
interface ReactiveMongoQueryExecution {
|
||||||
|
|
||||||
Object execute(Query query, Class<?> type, String collection);
|
Object execute(Query query, Class<?> type, String collection);
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ReactiveMongoQueryExecution} for collection returning queries.
|
|
||||||
*
|
|
||||||
* @author Mark Paluch
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
final class CollectionExecution implements ReactiveMongoQueryExecution {
|
|
||||||
|
|
||||||
private final @NonNull ReactiveMongoOperations operations;
|
|
||||||
private final Pageable pageable;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object execute(Query query, Class<?> type, String collection) {
|
|
||||||
return operations.find(query.with(pageable), type, collection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ReactiveMongoQueryExecution} for collection returning queries using tailable cursors.
|
* {@link ReactiveMongoQueryExecution} for collection returning queries using tailable cursors.
|
||||||
*
|
*
|
||||||
@@ -84,23 +68,6 @@ interface ReactiveMongoQueryExecution {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link ReactiveMongoQueryExecution} to return a single entity.
|
|
||||||
*
|
|
||||||
* @author Mark Paluch
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
final class SingleEntityExecution implements ReactiveMongoQueryExecution {
|
|
||||||
|
|
||||||
private final ReactiveMongoOperations operations;
|
|
||||||
private final boolean countProjection;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object execute(Query query, Class<?> type, String collection) {
|
|
||||||
return countProjection ? operations.count(query, type, collection) : operations.findOne(query, type, collection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link MongoQueryExecution} to execute geo-near queries.
|
* {@link MongoQueryExecution} to execute geo-near queries.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#createCountQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#createCountQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
|
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
|
||||||
@@ -127,7 +127,7 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isCountQuery()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean isCountQuery() {
|
protected boolean isCountQuery() {
|
||||||
@@ -136,10 +136,19 @@ public class ReactivePartTreeMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean isDeleteQuery() {
|
protected boolean isDeleteQuery() {
|
||||||
return tree.isDelete();
|
return tree.isDelete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isLimiting()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isLimiting() {
|
||||||
|
return tree.isLimiting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#createQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#createQuery(org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
protected Query createQuery(ConvertingParameterAccessor accessor) {
|
||||||
@@ -125,7 +125,7 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isCountQuery()
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isCountQuery()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean isCountQuery() {
|
protected boolean isCountQuery() {
|
||||||
@@ -134,11 +134,20 @@ public class ReactiveStringBasedMongoQuery extends AbstractReactiveMongoQuery {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isDeleteQuery()
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isDeleteQuery()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean isDeleteQuery() {
|
protected boolean isDeleteQuery() {
|
||||||
return this.isDeleteQuery;
|
return this.isDeleteQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.repository.query.AbstractReactiveMongoQuery#isLimiting()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isLimiting() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,6 +174,15 @@ public class StringBasedMongoQuery extends AbstractMongoQuery {
|
|||||||
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
return countBooleanValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery#isLimiting()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected boolean isLimiting() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static int countBooleanValues(boolean... values) {
|
private static int countBooleanValues(boolean... values) {
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.junit.rules.ExpectedException;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.data.domain.Example;
|
import org.springframework.data.domain.Example;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
@@ -1177,4 +1178,19 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
|
|||||||
public void readsClosedProjection() {
|
public void readsClosedProjection() {
|
||||||
assertThat(repository.findClosedProjectionBy()).isNotEmpty();
|
assertThat(repository.findClosedProjectionBy()).isNotEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void findFirstEntityReturnsFirstResultEvenForNonUniqueMatches() {
|
||||||
|
repository.findFirstBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IncorrectResultSizeDataAccessException.class) // DATAMONGO-1865
|
||||||
|
public void findSingleEntityThrowsErrorWhenNotUnique() {
|
||||||
|
repository.findPersonByLastnameLike(dave.getLastname());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IncorrectResultSizeDataAccessException.class) // DATAMONGO-1865
|
||||||
|
public void findOptionalSingleEntityThrowsErrorWhenNotUnique() {
|
||||||
|
repository.findOptionalPersonByLastnameLike(dave.getLastname());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -286,6 +287,15 @@ public interface PersonRepository extends MongoRepository<Person, String>, Query
|
|||||||
// DATAMONGO-950
|
// DATAMONGO-950
|
||||||
Page<Person> findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest);
|
Page<Person> findTop3ByLastnameStartingWith(String lastname, Pageable pageRequest);
|
||||||
|
|
||||||
|
// DATAMONGO-1865
|
||||||
|
Person findFirstBy(); // limits to 1 result if more, just return the first one
|
||||||
|
|
||||||
|
// DATAMONGO-1865
|
||||||
|
Person findPersonByLastnameLike(String firstname); // single person, error if more than one
|
||||||
|
|
||||||
|
// DATAMONGO-1865
|
||||||
|
Optional<Person> findOptionalPersonByLastnameLike(String firstname); // optional still, error when more than one
|
||||||
|
|
||||||
// DATAMONGO-1030
|
// DATAMONGO-1030
|
||||||
PersonSummaryDto findSummaryByLastname(String lastname);
|
PersonSummaryDto findSummaryByLastname(String lastname);
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import org.springframework.beans.factory.BeanClassLoaderAware;
|
|||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
@@ -300,6 +301,17 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
|
|||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void shouldErrorOnFindOneWithNonUniqueResult() {
|
||||||
|
StepVerifier.create(repository.findOneByLastname(dave.getLastname()))
|
||||||
|
.expectError(IncorrectResultSizeDataAccessException.class).verify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void shouldReturnFirstFindFirstWithMoreResults() {
|
||||||
|
StepVerifier.create(repository.findFirstByLastname(dave.getLastname())).expectNextCount(1).verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
|
interface ReactivePersonRepository extends ReactiveMongoRepository<Person, String> {
|
||||||
|
|
||||||
Flux<Person> findByLastname(String lastname);
|
Flux<Person> findByLastname(String lastname);
|
||||||
@@ -326,6 +338,8 @@ public class ReactiveMongoRepositoryTests implements BeanClassLoaderAware, BeanF
|
|||||||
Flux<GeoResult<Person>> findByLocationNear(Point point, Distance maxDistance, Pageable pageable);
|
Flux<GeoResult<Person>> findByLocationNear(Point point, Distance maxDistance, Pageable pageable);
|
||||||
|
|
||||||
Flux<Person> findPersonByLocationNear(Point point, Distance maxDistance);
|
Flux<Person> findPersonByLocationNear(Point point, Distance maxDistance);
|
||||||
|
|
||||||
|
Mono<Person> findFirstByLastname(String lastname);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ReactiveCappedCollectionRepository extends Repository<Capped, String> {
|
interface ReactiveCappedCollectionRepository extends Repository<Capped, String> {
|
||||||
|
|||||||
@@ -261,6 +261,18 @@ public class AbstractMongoQueryUnitTests {
|
|||||||
assertThat(query.execute(new Object[] { "lastname" }), is(reference));
|
assertThat(query.execute(new Object[] { "lastname" }), is(reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void limitingSingleEntityQueryCallsFirst() {
|
||||||
|
|
||||||
|
Person reference = new Person();
|
||||||
|
|
||||||
|
doReturn(reference).when(withQueryMock).firstValue();
|
||||||
|
|
||||||
|
AbstractMongoQuery query = createQueryForMethod("findFirstByLastname", String.class).setLimitingQuery(true);
|
||||||
|
|
||||||
|
assertThat(query.execute(new Object[] { "lastname" }), is(reference));
|
||||||
|
}
|
||||||
|
|
||||||
@Test // DATAMONGO-1872
|
@Test // DATAMONGO-1872
|
||||||
public void doesNotFixCollectionOnPreparation() {
|
public void doesNotFixCollectionOnPreparation() {
|
||||||
|
|
||||||
@@ -294,6 +306,7 @@ public class AbstractMongoQueryUnitTests {
|
|||||||
private static class MongoQueryFake extends AbstractMongoQuery {
|
private static class MongoQueryFake extends AbstractMongoQuery {
|
||||||
|
|
||||||
private boolean isDeleteQuery;
|
private boolean isDeleteQuery;
|
||||||
|
private boolean isLimitingQuery;
|
||||||
|
|
||||||
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
|
public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) {
|
||||||
super(method, operations);
|
super(method, operations);
|
||||||
@@ -319,10 +332,21 @@ public class AbstractMongoQueryUnitTests {
|
|||||||
return isDeleteQuery;
|
return isDeleteQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isLimiting() {
|
||||||
|
return isLimitingQuery;
|
||||||
|
}
|
||||||
|
|
||||||
public MongoQueryFake setDeleteQuery(boolean isDeleteQuery) {
|
public MongoQueryFake setDeleteQuery(boolean isDeleteQuery) {
|
||||||
this.isDeleteQuery = isDeleteQuery;
|
this.isDeleteQuery = isDeleteQuery;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MongoQueryFake setLimitingQuery(boolean limitingQuery) {
|
||||||
|
|
||||||
|
isLimitingQuery = limitingQuery;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface Repo extends MongoRepository<Person, Long> {
|
private interface Repo extends MongoRepository<Person, Long> {
|
||||||
@@ -344,6 +368,8 @@ public class AbstractMongoQueryUnitTests {
|
|||||||
Slice<Person> findByLastname(String lastname, Pageable page);
|
Slice<Person> findByLastname(String lastname, Pageable page);
|
||||||
|
|
||||||
Optional<Person> findByLastname(String lastname);
|
Optional<Person> findByLastname(String lastname);
|
||||||
|
|
||||||
|
Person findFirstByLastname(String lastname);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATAMONGO-1872
|
// DATAMONGO-1872
|
||||||
|
|||||||
@@ -185,6 +185,16 @@ public class PartTreeMongoQueryUnitTests {
|
|||||||
assertThat(query.getFieldsObject(), is(new Document()));
|
assertThat(query.getFieldsObject(), is(new Document()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void limitingReturnsTrueIfTreeIsLimiting() {
|
||||||
|
assertThat(createQueryForMethod("findFirstBy").isLimiting(), is(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test // DATAMONGO-1865
|
||||||
|
public void limitingReturnsFalseIfTreeIsNotLimiting() {
|
||||||
|
assertThat(createQueryForMethod("findPersonBy").isLimiting(), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object... args) {
|
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object... args) {
|
||||||
|
|
||||||
Class<?>[] types = new Class<?>[args.length];
|
Class<?>[] types = new Class<?>[args.length];
|
||||||
@@ -245,6 +255,8 @@ public class PartTreeMongoQueryUnitTests {
|
|||||||
List<Person> findBySex(Sex sex);
|
List<Person> findBySex(Sex sex);
|
||||||
|
|
||||||
OpenProjection findAllBy();
|
OpenProjection findAllBy();
|
||||||
|
|
||||||
|
Person findFirstBy();
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PersonProjection {
|
interface PersonProjection {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
|
|||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
import static org.mockito.Mockito.any;
|
||||||
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
@@ -35,6 +36,7 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import org.springframework.data.mongodb.core.ReactiveFindOperation.ReactiveFind;
|
||||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||||
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
import org.springframework.data.mongodb.core.convert.DbRefResolver;
|
||||||
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
|
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
|
||||||
@@ -56,6 +58,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
|
|||||||
* Unit tests for {@link ReactiveStringBasedMongoQuery}.
|
* Unit tests for {@link ReactiveStringBasedMongoQuery}.
|
||||||
*
|
*
|
||||||
* @author Mark Paluch
|
* @author Mark Paluch
|
||||||
|
* @author Christoph Strobl
|
||||||
*/
|
*/
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class ReactiveStringBasedMongoQueryUnitTests {
|
public class ReactiveStringBasedMongoQueryUnitTests {
|
||||||
@@ -64,11 +67,16 @@ public class ReactiveStringBasedMongoQueryUnitTests {
|
|||||||
|
|
||||||
@Mock ReactiveMongoOperations operations;
|
@Mock ReactiveMongoOperations operations;
|
||||||
@Mock DbRefResolver factory;
|
@Mock DbRefResolver factory;
|
||||||
|
@Mock ReactiveFind reactiveFind;
|
||||||
|
|
||||||
MongoConverter converter;
|
MongoConverter converter;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
|
|
||||||
|
when(operations.query(any())).thenReturn(reactiveFind);
|
||||||
|
when(reactiveFind.inCollection(anyString())).thenReturn(reactiveFind);
|
||||||
|
|
||||||
this.converter = new MappingMongoConverter(factory, new MongoMappingContext());
|
this.converter = new MappingMongoConverter(factory, new MongoMappingContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,17 +140,18 @@ public interface PersonRepository extends PagingAndSortingRepository<Person, Str
|
|||||||
|
|
||||||
Person findByShippingAddresses(Address address); <3>
|
Person findByShippingAddresses(Address address); <3>
|
||||||
|
|
||||||
Stream<Person> findAllBy(); <4>
|
Person findFirstByLastname(String lastname) <4>
|
||||||
|
|
||||||
|
Stream<Person> findAllBy(); <5>
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
<1> The method shows a query for all people with the given lastname. The query will be derived parsing the method name for constraints which can be concatenated with `And` and `Or`. Thus the method name will result in a query expression of `{"lastname" : lastname}`.
|
<1> The method shows a query for all people with the given lastname. The query will be derived parsing the method name for constraints which can be concatenated with `And` and `Or`. Thus the method name will result in a query expression of `{"lastname" : lastname}`.
|
||||||
<2> Applies pagination to a query. Just equip your method signature with a `Pageable` parameter and let the method return a `Page` instance and we will automatically page the query accordingly.
|
<2> Applies pagination to a query. Just equip your method signature with a `Pageable` parameter and let the method return a `Page` instance and we will automatically page the query accordingly.
|
||||||
<3> Shows that you can query based on properties which are not a primitive type.
|
<3> Shows that you can query based on properties which are not a primitive type. Errors with `IncorrectResultSizeDataAccessException` if more than one match found.
|
||||||
<4> Uses a Java 8 `Stream` which reads and converts individual elements while iterating the stream.
|
<4> Uses the `First` keyword to restrict the query to the very first result. Unlike <3> this method does not error if more than one match found.
|
||||||
|
<5> Uses a Java 8 `Stream` which reads and converts individual elements while iterating the stream.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
NOTE: Note that for version 1.0 we currently don't support referring to parameters that are mapped as `DBRef` in the domain class.
|
NOTE: Note that for version 1.0 we currently don't support referring to parameters that are mapped as `DBRef` in the domain class.
|
||||||
|
|
||||||
[cols="1,2,3", options="header"]
|
[cols="1,2,3", options="header"]
|
||||||
|
|||||||
@@ -52,15 +52,22 @@ We have a quite simple domain object here. Note that it has a property named `id
|
|||||||
----
|
----
|
||||||
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
|
public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
|
||||||
|
|
||||||
Flux<Person> findByFirstname(String firstname);
|
Flux<Person> findByFirstname(String firstname); <1>
|
||||||
|
|
||||||
Flux<Person> findByFirstname(Publisher<String> firstname);
|
Flux<Person> findByFirstname(Publisher<String> firstname); <2>
|
||||||
|
|
||||||
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable);
|
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
|
||||||
|
|
||||||
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);
|
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <4>
|
||||||
|
|
||||||
|
Mono<Person> findFirstByLastname(String lastname); <5>
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
<1> The method shows a query for all people with the given lastname. The query will be derived parsing the method name for constraints which can be concatenated with `And` and `Or`. Thus the method name will result in a query expression of `{"lastname" : lastname}`.
|
||||||
|
<2> The method shows a query for all people with the given firstname once the firstname becomes available via the given `Publisher`.
|
||||||
|
<3> Use `Pageable` to pass on offset and sorting parameters to the database.
|
||||||
|
<4> Find a single entity for given criteria. Errors on non unique results.
|
||||||
|
<5> Unless <4> the first entity is returned no matter what.
|
||||||
====
|
====
|
||||||
|
|
||||||
For JavaConfig use the `@EnableReactiveMongoRepositories` annotation. The annotation carries the very same attributes like the namespace element. If no base package is configured the infrastructure will scan the package of the annotated configuration class.
|
For JavaConfig use the `@EnableReactiveMongoRepositories` annotation. The annotation carries the very same attributes like the namespace element. If no base package is configured the infrastructure will scan the package of the annotated configuration class.
|
||||||
|
|||||||
Reference in New Issue
Block a user