From dd8fc1a5910db68b049b3ba85e641f7c5ebd7fef Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 5 Jul 2017 12:52:53 +0200 Subject: [PATCH] DATAMONGO-1738 - Move repository query execution to fluent operations API. We now use the fluent FindOperations API in AbstractMongoQuery and MongoQueryExecution instead of the MongoOperations. This allows us to eagerly resolve some general execution coordinates (which collection to query etc.) and thus simplify the eventual execution. Got rid of a couple of very simple QueryExecution implementations that can be replace by a simple lambda. Removed the need to read into a partially filled domain object and then map to the projection DTO as we can now tell the operations to read into the DTO directly. Adapted unit tests. Original pull request: #484. --- .../repository/query/AbstractMongoQuery.java | 66 ++---- .../repository/query/MongoQueryExecution.java | 224 +++++------------- .../query/AbstractMongoQueryUnitTests.java | 68 ++++-- .../query/MongoQueryExecutionUnitTests.java | 63 ++--- .../query/PartTreeMongoQueryUnitTests.java | 6 +- .../query/StringBasedMongoQueryUnitTests.java | 5 + 6 files changed, 180 insertions(+), 252 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java index d59efb045..ad41e7158 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/AbstractMongoQuery.java @@ -15,25 +15,20 @@ */ package org.springframework.data.mongodb.repository.query; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithProjection; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithQuery; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CollectionExecution; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.CountExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecution; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ExistsExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagingGeoNearExecution; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ResultProcessingConverter; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.ResultProcessingExecution; -import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SingleEntityExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.StreamExecution; import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; +import org.springframework.data.repository.query.ReturnedType; import org.springframework.util.Assert; /** @@ -48,7 +43,7 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { private final MongoQueryMethod method; private final MongoOperations operations; - private final EntityInstantiators instantiators; + private final FindOperationWithProjection findOperationWithProjection; /** * Creates a new {@link AbstractMongoQuery} from the given {@link MongoQueryMethod} and {@link MongoOperations}. @@ -63,7 +58,12 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { this.method = method; this.operations = operations; - this.instantiators = new EntityInstantiators(); + + ReturnedType returnedType = method.getResultProcessor().getReturnedType(); + + this.findOperationWithProjection = operations// + .query(returnedType.getDomainType())// + .inCollection(method.getEntityInformation().getCollectionName()); } /* @@ -86,52 +86,36 @@ public abstract class AbstractMongoQuery implements RepositoryQuery { applyQueryMetaAttributesWhenPresent(query); ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor); - String collection = method.getEntityInformation().getCollectionName(); + ReturnedType returnedType = processor.getReturnedType(); + FindOperationWithQuery find = findOperationWithProjection.as(returnedType.getTypeToRead()); - MongoQueryExecution execution = getExecution(query, accessor, - new ResultProcessingConverter(processor, operations, instantiators)); + MongoQueryExecution execution = getExecution(accessor, find); - return execution.execute(query, processor.getReturnedType().getDomainType(), collection); + return processor.processResult(execution.execute(query)); } - /** - * Returns the execution instance to use. - * - * @param query must not be {@literal null}. - * @param parameters must not be {@literal null}. - * @param accessor must not be {@literal null}. - * @return - */ - private MongoQueryExecution getExecution(Query query, MongoParameterAccessor accessor, - Converter resultProcessing) { - - if (method.isStreamQuery()) { - return new StreamExecution(operations, resultProcessing); - } - - return new ResultProcessingExecution(getExecutionToWrap(query, accessor), resultProcessing); - } - - private MongoQueryExecution getExecutionToWrap(Query query, MongoParameterAccessor accessor) { + private MongoQueryExecution getExecution(MongoParameterAccessor accessor, FindOperationWithQuery operation) { if (isDeleteQuery()) { return new DeleteExecution(operations, method); } else if (method.isGeoNearQuery() && method.isPageQuery()) { - return new PagingGeoNearExecution(operations, accessor, method.getReturnType(), this); + return new PagingGeoNearExecution(operations, method, accessor, this); } else if (method.isGeoNearQuery()) { - return new GeoNearExecution(operations, accessor, method.getReturnType()); + return new GeoNearExecution(operations, method, accessor); } else if (method.isSliceQuery()) { - return new SlicedExecution(operations, accessor.getPageable()); + return new SlicedExecution(operation, accessor.getPageable()); + } else if (method.isStreamQuery()) { + return new StreamExecution(operation); } else if (method.isCollectionQuery()) { - return new CollectionExecution(operations, accessor.getPageable()); + return q -> operation.matching(q.with(accessor.getPageable())).all(); } else if (method.isPageQuery()) { - return new PagedExecution(operations, accessor.getPageable()); + return new PagedExecution(operation, accessor.getPageable()); } else if (isCountQuery()) { - return new CountExecution(operations); + return q -> operation.matching(q).count(); } else if (isExistsQuery()) { - return new ExistsExecution(operations); + return q -> operation.matching(q).exists(); } else { - return new SingleEntityExecution(operations); + return q -> operation.matching(q).oneValue(); } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java index f9ac79696..18e8b8cf5 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java @@ -19,11 +19,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import org.springframework.core.convert.converter.Converter; -import org.springframework.data.convert.EntityInstantiators; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Range; @@ -34,16 +30,16 @@ import org.springframework.data.geo.GeoPage; import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.ExecutableFindOperation; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithQuery; +import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFindOperation; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.repository.query.ResultProcessor; -import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.support.PageableExecutionUtils; import org.springframework.data.util.CloseableIterator; import org.springframework.data.util.StreamUtils; import org.springframework.data.util.TypeInformation; -import org.springframework.util.ClassUtils; import com.mongodb.client.result.DeleteResult; @@ -58,7 +54,7 @@ import com.mongodb.client.result.DeleteResult; */ interface MongoQueryExecution { - Object execute(Query query, Class type, String collection); + Object execute(Query query); /** * {@link MongoQueryExecution} for collection returning queries. @@ -68,16 +64,16 @@ interface MongoQueryExecution { @RequiredArgsConstructor final class CollectionExecution implements MongoQueryExecution { - private final @NonNull MongoOperations operations; - private final Pageable pageable; + private final @NonNull ExecutableFindOperation.FindOperationWithQuery find; + private final @NonNull Pageable pageable; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override - public Object execute(Query query, Class type, String collection) { - return operations.find(query.with(pageable), type, collection); + public Object execute(Query query) { + return find.matching(query.with(pageable)).all(); } } @@ -89,24 +85,24 @@ interface MongoQueryExecution { * @since 1.5 */ @RequiredArgsConstructor - final class SlicedExecution implements MongoQueryExecution { + static final class SlicedExecution implements MongoQueryExecution { - private final @NonNull MongoOperations operations; + private final @NonNull FindOperationWithQuery find; private final @NonNull Pageable pageable; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) - public Object execute(Query query, Class type, String collection) { + public Object execute(Query query) { int pageSize = pageable.getPageSize(); // Apply Pageable but tweak limit to peek into next page Query modifiedQuery = query.with(pageable).limit(pageSize + 1); - List result = operations.find(modifiedQuery, type, collection); + List result = find.matching(modifiedQuery).all(); boolean hasNext = result.size() > pageSize; @@ -121,19 +117,21 @@ interface MongoQueryExecution { * @author Mark Paluch */ @RequiredArgsConstructor - final class PagedExecution implements MongoQueryExecution { + static final class PagedExecution implements MongoQueryExecution { - private final @NonNull MongoOperations operations; + private final @NonNull FindOperationWithQuery operation; private final @NonNull Pageable pageable; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override - public Object execute(final Query query, final Class type, final String collection) { + public Object execute(Query query) { - final int overallLimit = query.getLimit(); + int overallLimit = query.getLimit(); + + TerminatingFindOperation matching = operation.matching(query); // Apply raw pagination query.with(pageable); @@ -143,35 +141,14 @@ interface MongoQueryExecution { query.limit((int) (overallLimit - pageable.getOffset())); } - return PageableExecutionUtils.getPage(operations.find(query, type, collection), pageable, () -> { + return PageableExecutionUtils.getPage(matching.all(), pageable, () -> { - long count = operations.count(query, type, collection); + long count = matching.count(); return overallLimit != 0 ? Math.min(count, overallLimit) : count; - }); } } - /** - * {@link MongoQueryExecution} to return a single entity. - * - * @author Oliver Gierke - */ - @RequiredArgsConstructor - final class SingleEntityExecution implements MongoQueryExecution { - - private final MongoOperations operations; - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public Object execute(Query query, Class type, String collection) { - return operations.findOne(query, type, collection); - } - } - /** * {@link MongoQueryExecution} to perform a count projection. * @@ -182,36 +159,15 @@ interface MongoQueryExecution { @RequiredArgsConstructor static final class CountExecution implements MongoQueryExecution { - private final MongoOperations operations; + private final @NonNull FindOperationWithQuery operation; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override - public Object execute(Query query, Class type, String collection) { - return operations.count(query, type, collection); - } - } - - /** - * {@link MongoQueryExecution} to perform an exists projection. - * - * @author Mark Paluch - * @since 1.10 - */ - @RequiredArgsConstructor - static final class ExistsExecution implements MongoQueryExecution { - - private final MongoOperations operations; - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public Object execute(Query query, Class type, String collection) { - return operations.exists(query, type, collection); + public Object execute(Query query) { + return operation.count(); } } @@ -221,25 +177,28 @@ interface MongoQueryExecution { * @author Oliver Gierke */ @RequiredArgsConstructor - class GeoNearExecution implements MongoQueryExecution { + static class GeoNearExecution implements MongoQueryExecution { private final MongoOperations operations; + private final MongoQueryMethod method; private final MongoParameterAccessor accessor; - private final TypeInformation returnType; /* * (non-Javadoc) * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ @Override - public Object execute(Query query, Class type, String collection) { + public Object execute(Query query) { - GeoResults results = doExecuteQuery(query, type, collection); - return isListOfGeoResult() ? results.getContent() : results; + GeoResults results = doExecuteQuery(query); + return isListOfGeoResult(method.getReturnType()) ? results.getContent() : results; } @SuppressWarnings("unchecked") - protected GeoResults doExecuteQuery(Query query, Class type, String collection) { + protected GeoResults doExecuteQuery(Query query) { + + Class type = method.getReturnedObjectType(); + String collection = method.getEntityInformation().getCollectionName(); Point nearLocation = accessor.getGeoNearLocation(); NearQuery nearQuery = NearQuery.near(nearLocation); @@ -261,7 +220,7 @@ interface MongoQueryExecution { return (GeoResults) operations.geoNear(nearQuery, type, collection); } - private boolean isListOfGeoResult() { + private static boolean isListOfGeoResult(TypeInformation returnType) { if (!returnType.getType().equals(List.class)) { return false; @@ -278,20 +237,22 @@ interface MongoQueryExecution { * @author Oliver Gierke * @author Mark Paluch */ - final class PagingGeoNearExecution extends GeoNearExecution { + static final class PagingGeoNearExecution extends GeoNearExecution { private final MongoOperations operations; + private final MongoQueryMethod method; private final MongoParameterAccessor accessor; private final AbstractMongoQuery mongoQuery; - public PagingGeoNearExecution(MongoOperations operations, MongoParameterAccessor accessor, - TypeInformation returnType, AbstractMongoQuery query) { + public PagingGeoNearExecution(MongoOperations operations, MongoQueryMethod method, MongoParameterAccessor accessor, + AbstractMongoQuery query) { - super(operations, accessor, returnType); + super(operations, method, accessor); this.accessor = accessor; this.operations = operations; this.mongoQuery = query; + this.method = method; } /* @@ -299,19 +260,20 @@ interface MongoQueryExecution { * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution.GeoNearExecution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) */ @Override - public Object execute(Query query, Class type, final String collection) { + public Object execute(Query query) { - GeoResults geoResults = doExecuteQuery(query, type, collection); + String collectionName = method.getEntityInformation().getCollectionName(); + + GeoResults geoResults = doExecuteQuery(query); Page> page = PageableExecutionUtils.getPage(geoResults.getContent(), accessor.getPageable(), () -> { - ConvertingParameterAccessor parameterAccessor = new ConvertingParameterAccessor(operations.getConverter(), - accessor); Query countQuery = mongoQuery - .applyQueryMetaAttributesWhenPresent(mongoQuery.createCountQuery(parameterAccessor)); + .createCountQuery(new ConvertingParameterAccessor(operations.getConverter(), accessor)); + countQuery = mongoQuery.applyQueryMetaAttributesWhenPresent(countQuery); - return operations.count(countQuery, collection); + return operations.count(countQuery, collectionName); }); @@ -326,23 +288,26 @@ interface MongoQueryExecution { * @since 1.5 */ @RequiredArgsConstructor - final class DeleteExecution implements MongoQueryExecution { + static final class DeleteExecution implements MongoQueryExecution { private final MongoOperations operations; private final MongoQueryMethod method; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override - public Object execute(Query query, Class type, String collection) { + public Object execute(Query query) { + + String collectionName = method.getEntityInformation().getCollectionName(); + Class type = method.getEntityInformation().getJavaType(); if (method.isCollectionQuery()) { - return operations.findAllAndRemove(query, type, collection); + return operations.findAllAndRemove(query, type, collectionName); } - DeleteResult writeResult = operations.remove(query, type, collection); + DeleteResult writeResult = operations.remove(query, type, collectionName); return writeResult != null ? writeResult.getDeletedCount() : 0L; } } @@ -352,82 +317,21 @@ interface MongoQueryExecution { * @since 1.7 */ @RequiredArgsConstructor - final class StreamExecution implements MongoQueryExecution { + static final class StreamExecution implements MongoQueryExecution { - private final @NonNull MongoOperations operations; - private final @NonNull Converter resultProcessing; + private final @NonNull FindOperationWithQuery operation; /* * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) + * @see org.springframework.data.mongodb.repository.query.MongoQueryExecution#execute(org.springframework.data.mongodb.core.query.Query) */ @Override @SuppressWarnings("unchecked") - public Object execute(Query query, Class type, String collection) { + public Object execute(Query query) { - return StreamUtils.createStreamFromIterator((CloseableIterator) operations.stream(query, type)) - .map(new Function() { + TerminatingFindOperation matching = operation.matching(query); - @Override - public Object apply(Object t) { - return resultProcessing.convert(t); - } - }); - } - } - - /** - * An {@link MongoQueryExecution} that wraps the results of the given delegate with the given result processing. - * - * @author Oliver Gierke - * @since 1.9 - */ - @RequiredArgsConstructor - final class ResultProcessingExecution implements MongoQueryExecution { - - private final @NonNull MongoQueryExecution delegate; - private final @NonNull Converter converter; - - /* - * (non-Javadoc) - * @see org.springframework.data.mongodb.repository.query.AbstractMongoQuery.Execution#execute(org.springframework.data.mongodb.core.query.Query, java.lang.Class, java.lang.String) - */ - @Override - public Object execute(Query query, Class type, String collection) { - return converter.convert(delegate.execute(query, type, collection)); - } - } - - /** - * A {@link Converter} to post-process all source objects using the given {@link ResultProcessor}. - * - * @author Oliver Gierke - * @since 1.9 - */ - @RequiredArgsConstructor - final class ResultProcessingConverter implements Converter { - - private final @NonNull ResultProcessor processor; - private final @NonNull MongoOperations operations; - private final @NonNull EntityInstantiators instantiators; - - /* - * (non-Javadoc) - * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) - */ - @Override - public Object convert(Object source) { - - ReturnedType returnedType = processor.getReturnedType(); - - if (ClassUtils.isPrimitiveOrWrapper(returnedType.getReturnedType())) { - return source; - } - - Converter converter = new DtoInstantiatingConverter(returnedType.getReturnedType(), - operations.getConverter().getMappingContext(), instantiators); - - return processor.processResult(source, converter); + return StreamUtils.createStreamFromIterator((CloseableIterator) matching.stream()); } } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java index 044d19f9c..be12b8ae5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/AbstractMongoQueryUnitTests.java @@ -17,6 +17,8 @@ package org.springframework.data.mongodb.repository.query; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -24,12 +26,10 @@ import java.util.List; import java.util.Optional; import org.bson.Document; -import org.hamcrest.core.Is; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @@ -39,6 +39,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithProjection; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithQuery; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.Person; import org.springframework.data.mongodb.core.convert.DbRefResolver; @@ -69,6 +72,9 @@ import com.mongodb.client.result.DeleteResult; public class AbstractMongoQueryUnitTests { @Mock MongoOperations mongoOperationsMock; + @Mock FindOperation findOperationMock; + @Mock FindOperationWithProjection withProjectionMock; + @Mock FindOperationWithQuery withQueryMock; @Mock BasicMongoPersistentEntity persitentEntityMock; @Mock MongoMappingContext mappingContextMock; @Mock WriteResult writeResultMock; @@ -87,26 +93,27 @@ public class AbstractMongoQueryUnitTests { converter.afterPropertiesSet(); doReturn(converter).when(mongoOperationsMock).getConverter(); + doReturn(findOperationMock).when(mongoOperationsMock).query(any()); + doReturn(withProjectionMock).when(findOperationMock).inCollection(any()); + doReturn(withQueryMock).when(withProjectionMock).as(any()); + doReturn(withQueryMock).when(withQueryMock).matching(any()); } - @SuppressWarnings("unchecked") @Test // DATAMONGO-566 public void testDeleteExecutionCallsRemoveCorreclty() { createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); - verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); - verify(mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class), - Matchers.anyString()); + verify(mongoOperationsMock, times(1)).remove(any(), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(0)).find(any(), any(), any()); } - @SuppressWarnings("unchecked") @Test // DATAMONGO-566, DATAMONGO-1040 public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() { createQueryForMethod("deleteByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" }); - verify(mongoOperationsMock, times(1)).findAllAndRemove(Mockito.any(Query.class), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(1)).findAllAndRemove(any(), eq(Person.class), eq("persons")); } @Test // DATAMONGO-566 @@ -115,21 +122,20 @@ public class AbstractMongoQueryUnitTests { MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class); query.setDeleteQuery(true); - assertThat(query.execute(new Object[] { "fake" }), Is. is(0L)); + assertThat(query.execute(new Object[] { "fake" }), is(0L)); } @Test // DATAMONGO-566, DATAMONGO-978 public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() { when(deleteResultMock.getDeletedCount()).thenReturn(100L); - when(mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons"))) - .thenReturn(deleteResultMock); + when(mongoOperationsMock.remove(any(), eq(Person.class), eq("persons"))).thenReturn(deleteResultMock); MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class); query.setDeleteQuery(true); assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L)); - verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons")); + verify(mongoOperationsMock, times(1)).remove(any(), eq(Person.class), eq("persons")); } @Test // DATAMONGO-957 @@ -140,7 +146,9 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock).as(Person.class); + verify(withQueryMock).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); assertThat(captor.getValue().getMeta().getComment(), nullValue()); } @@ -153,7 +161,10 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock).as(Person.class); + verify(withQueryMock).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); + assertThat(captor.getValue().getMeta().getComment(), is("comment")); } @@ -165,7 +176,10 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock).as(Person.class); + verify(withQueryMock).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); + assertThat(captor.getValue().getMeta().getComment(), is("comment")); } @@ -177,7 +191,10 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock).as(Person.class); + verify(withQueryMock).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); + assertThat(captor.getValue().getMeta().getComment(), is("comment")); } @@ -193,7 +210,9 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock, times(2)).as(Person.class); + verify(withQueryMock, times(2)).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); assertThat(captor.getAllValues().get(0).getSkip(), is(0L)); assertThat(captor.getAllValues().get(1).getSkip(), is(10L)); @@ -211,7 +230,9 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock, times(2)).as(Person.class); + verify(withQueryMock, times(2)).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); assertThat(captor.getAllValues().get(0).getLimit(), is(11)); assertThat(captor.getAllValues().get(1).getLimit(), is(11)); @@ -229,7 +250,9 @@ public class AbstractMongoQueryUnitTests { ArgumentCaptor captor = ArgumentCaptor.forClass(Query.class); - verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons")); + verify(withProjectionMock, times(2)).as(Person.class); + verify(withQueryMock, times(2)).matching(captor.capture()); + verify(findOperationMock).inCollection("persons"); Document expectedSortObject = new Document().append("bar", -1); assertThat(captor.getAllValues().get(0).getSortObject(), is(expectedSortObject)); @@ -240,8 +263,8 @@ public class AbstractMongoQueryUnitTests { public void doesNotTryToPostProcessQueryResultIntoWrapperType() { Person reference = new Person(); - when(mongoOperationsMock.findOne(Mockito.any(Query.class), eq(Person.class), eq("persons"))).// - thenReturn(reference); + + doReturn(reference).when(withQueryMock).oneValue(); AbstractMongoQuery query = createQueryForMethod("findByLastname", String.class); @@ -269,7 +292,6 @@ public class AbstractMongoQueryUnitTests { private static class MongoQueryFake extends AbstractMongoQuery { private boolean isCountQuery; - private boolean isExistsQuery; private boolean isDeleteQuery; public MongoQueryFake(MongoQueryMethod method, MongoOperations operations) { @@ -310,7 +332,7 @@ public class AbstractMongoQueryUnitTests { List findByFirstname(String firstname); - @Meta(comment = "comment", flags = {org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT}) + @Meta(comment = "comment", flags = { org.springframework.data.mongodb.core.query.Meta.CursorOption.NO_TIMEOUT }) Page findByFirstname(String firstnanme, Pageable pageable); @Meta(comment = "comment") diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java index 54ef9ed59..033be0ff0 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/MongoQueryExecutionUnitTests.java @@ -15,8 +15,7 @@ */ package org.springframework.data.mongodb.repository.query; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -36,6 +35,9 @@ import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperationWithQuery; +import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFindOperation; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.MappingMongoConverter; @@ -50,7 +52,6 @@ import org.springframework.data.projection.SpelAwareProxyProjectionFactory; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; -import org.springframework.data.util.ClassTypeInformation; import org.springframework.util.ReflectionUtils; /** @@ -64,6 +65,8 @@ import org.springframework.util.ReflectionUtils; public class MongoQueryExecutionUnitTests { @Mock MongoOperations mongoOperationsMock; + @Mock FindOperation findOperationMock; + @Mock FindOperationWithQuery operationMock; @Mock DbRefResolver dbRefResolver; Point POINT = new Point(10, 20); @@ -79,46 +82,54 @@ public class MongoQueryExecutionUnitTests { public void setUp() throws Exception { MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, context); + when(mongoOperationsMock.getConverter()).thenReturn(converter); + when(mongoOperationsMock.query(any(Class.class))).thenReturn(findOperationMock); } @Test // DATAMONGO-1464 public void pagedExecutionShouldNotGenerateCountQueryIfQueryReportedNoResults() { - when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) - .thenReturn(Collections. emptyList()); + TerminatingFindOperation terminating = mock(TerminatingFindOperation.class); - PagedExecution execution = new PagedExecution(mongoOperationsMock, PageRequest.of(0, 10)); - execution.execute(new Query(), Person.class, "person"); + doReturn(terminating).when(operationMock).matching(any(Query.class)); + doReturn(Collections.emptyList()).when(terminating).all(); - verify(mongoOperationsMock).find(any(Query.class), eq(Person.class), eq("person")); - verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); + PagedExecution execution = new PagedExecution(operationMock, PageRequest.of(0, 10)); + execution.execute(new Query()); + + verify(terminating).all(); + verify(terminating, never()).count(); } @Test // DATAMONGO-1464 public void pagedExecutionShouldUseCountFromResultWithOffsetAndResultsWithinPageSize() { - when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) - .thenReturn(Arrays.asList(new Person(), new Person(), new Person(), new Person())); + TerminatingFindOperation terminating = mock(TerminatingFindOperation.class); - PagedExecution execution = new PagedExecution(mongoOperationsMock, PageRequest.of(0, 10)); - execution.execute(new Query(), Person.class, "person"); + doReturn(terminating).when(operationMock).matching(any(Query.class)); + doReturn(Arrays.asList(new Person(), new Person(), new Person(), new Person())).when(terminating).all(); - verify(mongoOperationsMock).find(any(Query.class), eq(Person.class), eq("person")); - verify(mongoOperationsMock, never()).count(any(Query.class), eq(Person.class), eq("person")); + PagedExecution execution = new PagedExecution(operationMock, PageRequest.of(0, 10)); + execution.execute(new Query()); + + verify(terminating).all(); + verify(terminating, never()).count(); } @Test // DATAMONGO-1464 public void pagedExecutionRetrievesObjectsForPageableOutOfRange() throws Exception { - when(mongoOperationsMock.find(any(Query.class), eq(Person.class), eq("person"))) - .thenReturn(Collections. emptyList()); + TerminatingFindOperation terminating = mock(TerminatingFindOperation.class); - PagedExecution execution = new PagedExecution(mongoOperationsMock, PageRequest.of(2, 10)); - execution.execute(new Query(), Person.class, "person"); + doReturn(terminating).when(operationMock).matching(any(Query.class)); + doReturn(Collections.emptyList()).when(terminating).all(); - verify(mongoOperationsMock).find(any(Query.class), eq(Person.class), eq("person")); - verify(mongoOperationsMock).count(any(Query.class), eq(Person.class), eq("person")); + PagedExecution execution = new PagedExecution(operationMock, PageRequest.of(2, 10)); + execution.execute(new Query()); + + verify(terminating).all(); + verify(terminating).count(); } @Test // DATAMONGO-1464 @@ -133,9 +144,8 @@ public class MongoQueryExecutionUnitTests { when(mongoOperationsMock.geoNear(any(NearQuery.class), eq(Person.class), eq("person"))) .thenReturn(new GeoResults(Arrays.asList(result, result, result, result))); - PagingGeoNearExecution execution = new PagingGeoNearExecution(mongoOperationsMock, accessor, - ClassTypeInformation.fromReturnTypeOf(method), query); - execution.execute(new Query(), Person.class, "person"); + PagingGeoNearExecution execution = new PagingGeoNearExecution(mongoOperationsMock, queryMethod, accessor, query); + execution.execute(new Query()); verify(mongoOperationsMock).geoNear(any(NearQuery.class), eq(Person.class), eq("person")); verify(mongoOperationsMock, never()).count(any(Query.class), eq("person")); @@ -152,9 +162,8 @@ public class MongoQueryExecutionUnitTests { when(mongoOperationsMock.geoNear(any(NearQuery.class), eq(Person.class), eq("person"))) .thenReturn(new GeoResults(Collections.> emptyList())); - PagingGeoNearExecution execution = new PagingGeoNearExecution(mongoOperationsMock, accessor, - ClassTypeInformation.fromReturnTypeOf(method), query); - execution.execute(new Query(), Person.class, "person"); + PagingGeoNearExecution execution = new PagingGeoNearExecution(mongoOperationsMock, queryMethod, accessor, query); + execution.execute(new Query()); verify(mongoOperationsMock).geoNear(any(NearQuery.class), eq(Person.class), eq("person")); verify(mongoOperationsMock).count(any(Query.class), eq("person")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java index 15bea55e7..c26f99fad 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/PartTreeMongoQueryUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.springframework.data.mongodb.core.query.IsTextQuery.*; @@ -33,6 +34,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; @@ -63,6 +65,7 @@ import com.mongodb.util.JSONParseException; public class PartTreeMongoQueryUnitTests { @Mock MongoOperations mongoOperationsMock; + @Mock FindOperation findOperationMock; MongoMappingContext mappingContext; @@ -75,7 +78,8 @@ public class PartTreeMongoQueryUnitTests { DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class)); MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext); - when(mongoOperationsMock.getConverter()).thenReturn(converter); + doReturn(converter).when(mongoOperationsMock).getConverter(); + doReturn(findOperationMock).when(mongoOperationsMock).query(any()); } @Test // DATAMOGO-952 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java index ca59d91f2..fcedd832d 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/query/StringBasedMongoQueryUnitTests.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.query; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.lang.reflect.Method; @@ -36,6 +37,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.mongodb.core.DocumentTestUtils; +import org.springframework.data.mongodb.core.ExecutableFindOperation.FindOperation; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.convert.DbRefResolver; import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper; @@ -67,6 +69,7 @@ public class StringBasedMongoQueryUnitTests { SpelExpressionParser PARSER = new SpelExpressionParser(); @Mock MongoOperations operations; + @Mock FindOperation findOperation; @Mock DbRefResolver factory; MongoConverter converter; @@ -75,6 +78,8 @@ public class StringBasedMongoQueryUnitTests { public void setUp() { this.converter = new MappingMongoConverter(factory, new MongoMappingContext()); + + doReturn(findOperation).when(operations).query(any()); } @Test