Browse Source

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.
pull/534/head
Christoph Strobl 8 years ago committed by Mark Paluch
parent
commit
0ec840e34a
  1. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java
  2. 50
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java
  3. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java
  4. 35
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryExecution.java
  5. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java
  6. 15
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java
  7. 9
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java
  8. 16
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java
  9. 10
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java
  10. 14
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java
  11. 26
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java
  12. 12
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java
  13. 8
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java
  14. 11
      src/main/asciidoc/reference/mongo-repositories.adoc
  15. 15
      src/main/asciidoc/reference/reactive-mongo-repositories.adoc

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java

@ -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();
} }

50
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractReactiveMongoQuery.java

@ -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();
} }

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQuery.java

@ -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();
}
} }

35
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveMongoQueryExecution.java

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

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactivePartTreeMongoQuery.java

@ -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();
}
} }

15
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQuery.java

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

9
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQuery.java

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

16
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/AbstractPersonRepositoryIntegrationTests.java

@ -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());
}
} }

10
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepository.java

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

14
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/ReactiveMongoRepositoryTests.java

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

26
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java

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

12
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java

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

8
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/ReactiveStringBasedMongoQueryUnitTests.java

@ -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());
} }

11
src/main/asciidoc/reference/mongo-repositories.adoc

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

15
src/main/asciidoc/reference/reactive-mongo-repositories.adoc

@ -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); <2>
Flux<Person> findByFirstname(Publisher<String> firstname); Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <4>
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); 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.

Loading…
Cancel
Save