DATAMONGO-1345 - Added support for projections on repository methods.

Related tickets: DATACMNS-89.
This commit is contained in:
Oliver Gierke
2015-12-01 00:25:13 +01:00
parent 63a619dddf
commit 727271e68c
11 changed files with 232 additions and 98 deletions

View File

@@ -90,6 +90,15 @@ public class ConvertingParameterAccessor implements MongoParameterAccessor {
return delegate.getSort();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection()
*/
@Override
public Class<?> getDynamicProjection() {
return delegate.getDynamicProjection();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getBindableValue(int)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@ import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.ClassTypeInformation;
@@ -56,12 +57,15 @@ public class MongoQueryMethod extends QueryMethod {
/**
* Creates a new {@link MongoQueryMethod} from the given {@link Method}.
*
* @param method
* @param method must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @param projectionFactory must not be {@literal null}.
* @param mappingContext must not be {@literal null}.
*/
public MongoQueryMethod(Method method, RepositoryMetadata metadata,
public MongoQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory projectionFactory,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
super(method, metadata);
super(method, metadata, projectionFactory);
Assert.notNull(mappingContext, "MappingContext must not be null!");

View File

@@ -20,10 +20,13 @@ import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.util.StringUtils;
@@ -87,6 +90,19 @@ public class PartTreeMongoQuery extends AbstractMongoQuery {
String fieldSpec = this.getQueryMethod().getFieldSpecification();
if (!StringUtils.hasText(fieldSpec)) {
ResultProcessor processor = getQueryMethod().getResultProcessor();
ReturnedType returnedType = processor.withDynamicProjection(accessor).getReturnedType();
if (returnedType.isProjecting()) {
Field fields = query.fields();
for (String field : returnedType.getInputProperties()) {
fields.include(field);
}
}
return query;
}

View File

@@ -30,6 +30,7 @@ import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.query.MongoQueryMethod;
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
import org.springframework.data.mongodb.repository.query.StringBasedMongoQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
@@ -133,13 +134,15 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
this.evaluationContextProvider = evaluationContextProvider;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
*/
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
@Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, mappingContext);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, mappingContext);
String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {

View File

@@ -53,7 +53,9 @@ import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
@@ -69,27 +71,23 @@ import com.mongodb.WriteResult;
@RunWith(MockitoJUnitRunner.class)
public class AbstractMongoQueryUnitTests {
@Mock RepositoryMetadata metadataMock;
@Mock MongoOperations mongoOperationsMock;
@Mock @SuppressWarnings("rawtypes") BasicMongoPersistentEntity persitentEntityMock;
@Mock BasicMongoPersistentEntity<?> persitentEntityMock;
@Mock MongoMappingContext mappingContextMock;
@Mock WriteResult writeResultMock;
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() {
when(metadataMock.getDomainType()).thenReturn((Class) Person.class);
when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class);
when(persitentEntityMock.getCollection()).thenReturn("persons");
when(mappingContextMock.getPersistentEntity(Matchers.any(Class.class))).thenReturn(persitentEntityMock);
when(persitentEntityMock.getType()).thenReturn(Person.class);
doReturn("persons").when(persitentEntityMock).getCollection();
doReturn(persitentEntityMock).when(mappingContextMock).getPersistentEntity(Matchers.any(Class.class));
doReturn(Person.class).when(persitentEntityMock).getType();
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContextMock);
converter.afterPropertiesSet();
when(mongoOperationsMock.getConverter()).thenReturn(converter);
doReturn(converter).when(mongoOperationsMock).getConverter();
}
/**
@@ -101,8 +99,8 @@ public class AbstractMongoQueryUnitTests {
createQueryForMethod("deletePersonByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
verify(this.mongoOperationsMock, times(0)).find(Matchers.any(Query.class), Matchers.any(Class.class),
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());
}
@@ -114,13 +112,12 @@ public class AbstractMongoQueryUnitTests {
@Test
public void testDeleteExecutionLoadsListOfRemovedDocumentsWhenReturnTypeIsCollectionLike() {
when(this.mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString()))
when(mongoOperationsMock.find(Matchers.any(Query.class), Matchers.any(Class.class), Matchers.anyString()))
.thenReturn(Arrays.asList(new Person(new ObjectId(new Date()), "bar")));
createQueryForMethod("deleteByLastname", String.class).setDeleteQuery(true).execute(new Object[] { "booh" });
verify(this.mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class),
eq("persons"));
verify(mongoOperationsMock, times(1)).findAllAndRemove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
}
/**
@@ -143,14 +140,14 @@ public class AbstractMongoQueryUnitTests {
public void testDeleteExecutionReturnsNrDocumentsDeletedFromWriteResult() {
when(writeResultMock.getN()).thenReturn(100);
when(this.mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons")))
when(mongoOperationsMock.remove(Matchers.any(Query.class), eq(Person.class), eq("persons")))
.thenReturn(writeResultMock);
MongoQueryFake query = createQueryForMethod("deletePersonByLastname", String.class);
query.setDeleteQuery(true);
assertThat(query.execute(new Object[] { "fake" }), is((Object) 100L));
verify(this.mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(1)).remove(Matchers.any(Query.class), eq(Person.class), eq("persons"));
}
/**
@@ -164,7 +161,7 @@ public class AbstractMongoQueryUnitTests {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(1)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), nullValue());
}
@@ -195,7 +192,7 @@ public class AbstractMongoQueryUnitTests {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(1)).count(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getValue().getMeta().getComment(), is("comment"));
}
@@ -229,7 +226,7 @@ public class AbstractMongoQueryUnitTests {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getAllValues().get(0).getSkip(), is(0));
assertThat(captor.getAllValues().get(1).getSkip(), is(10));
@@ -250,7 +247,7 @@ public class AbstractMongoQueryUnitTests {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
assertThat(captor.getAllValues().get(0).getLimit(), is(11));
assertThat(captor.getAllValues().get(1).getLimit(), is(11));
@@ -271,7 +268,7 @@ public class AbstractMongoQueryUnitTests {
ArgumentCaptor<Query> captor = ArgumentCaptor.forClass(Query.class);
verify(this.mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
verify(mongoOperationsMock, times(2)).find(captor.capture(), eq(Person.class), eq("persons"));
DBObject expectedSortObject = new BasicDBObjectBuilder().add("bar", -1).get();
assertThat(captor.getAllValues().get(0).getSortObject(), is(expectedSortObject));
@@ -298,7 +295,9 @@ public class AbstractMongoQueryUnitTests {
try {
Method method = Repo.class.getMethod(methodName, paramTypes);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContextMock);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory,
mappingContextMock);
return new MongoQueryFake(queryMethod, mongoOperationsMock);

View File

@@ -30,6 +30,8 @@ import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.projection.ProjectionFactory;
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;
@@ -42,15 +44,16 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
*/
public class MongoParametersParameterAccessorUnitTests {
private static final Distance DISTANCE = new Distance(2.5, Metrics.KILOMETERS);
private static final RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class);
private static final MongoMappingContext context = new MongoMappingContext();
Distance DISTANCE = new Distance(2.5, Metrics.KILOMETERS);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class);
MongoMappingContext context = new MongoMappingContext();
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
@Test
public void returnsNullForDistanceIfNoneAvailable() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { new Point(10, 20) });
@@ -61,10 +64,10 @@ public class MongoParametersParameterAccessorUnitTests {
public void returnsDistanceIfAvailable() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
new Point(10, 20), DISTANCE });
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { new Point(10, 20), DISTANCE });
assertThat(accessor.getDistanceRange().getUpperBound(), is(DISTANCE));
}
@@ -75,10 +78,10 @@ public class MongoParametersParameterAccessorUnitTests {
public void shouldReturnAsFullTextStringWhenNoneDefinedForMethod() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Distance.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
new Point(10, 20), DISTANCE });
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { new Point(10, 20), DISTANCE });
assertThat(accessor.getFullText(), IsNull.nullValue());
}
@@ -89,10 +92,10 @@ public class MongoParametersParameterAccessorUnitTests {
public void shouldProperlyConvertTextCriteria() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, TextCriteria.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { "spring",
TextCriteria.forDefaultLanguage().matching("data") });
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { "spring", TextCriteria.forDefaultLanguage().matching("data") });
assertThat(accessor.getFullText().getCriteriaObject().toString(),
equalTo("{ \"$text\" : { \"$search\" : \"data\"}}"));
}
@@ -104,13 +107,13 @@ public class MongoParametersParameterAccessorUnitTests {
public void shouldDetectMinAndMaxDistance() throws NoSuchMethodException, SecurityException {
Method method = PersonRepository.class.getMethod("findByLocationNear", Point.class, Range.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, context);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, factory, context);
Distance min = new Distance(10, Metrics.KILOMETERS);
Distance max = new Distance(20, Metrics.KILOMETERS);
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] {
new Point(10, 20), Distance.between(min, max) });
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { new Point(10, 20), Distance.between(min, max) });
Range<Distance> range = accessor.getDistanceRange();

View File

@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.query;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Matchers.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.query.Query.*;
@@ -54,6 +55,7 @@ import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.parser.PartTree;
@@ -137,8 +139,8 @@ public class MongoQueryCreatorUnitTests {
Point point = new Point(10, 20);
Distance distance = new Distance(2.5, Metrics.KILOMETERS);
Query query = query(where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname")
.is("Dave"));
Query query = query(
where("location").nearSphere(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
assertBindsDistanceToQuery(point, distance, query);
}
@@ -148,8 +150,8 @@ public class MongoQueryCreatorUnitTests {
Point point = new Point(10, 20);
Distance distance = new Distance(2.5);
Query query = query(where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname")
.is("Dave"));
Query query = query(
where("location").near(point).maxDistance(distance.getNormalizedValue()).and("firstname").is("Dave"));
assertBindsDistanceToQuery(point, distance, query);
}
@@ -299,9 +301,9 @@ public class MongoQueryCreatorUnitTests {
Method method = PersonRepository.class.getMethod("findByLocationNearAndFirstname", Point.class, Distance.class,
String.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
new MongoMappingContext());
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod, new Object[] { point, distance,
"Dave" });
new SpelAwareProxyProjectionFactory(), new MongoMappingContext());
MongoParameterAccessor accessor = new MongoParametersParameterAccessor(queryMethod,
new Object[] { point, distance, "Dave" });
Query query = new MongoQueryCreator(tree, new ConvertingParameterAccessor(converter, accessor), context)
.createQuery();

View File

@@ -36,6 +36,8 @@ import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Contact;
import org.springframework.data.mongodb.repository.Meta;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
@@ -57,10 +59,7 @@ public class MongoQueryMethodUnitTests {
@Test
public void detectsCollectionFromRepoTypeIfReturnTypeNotAssignable() throws Exception {
Method method = SampleRepository.class.getMethod("method");
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
context);
MongoQueryMethod queryMethod = queryMethod(SampleRepository.class, "method");
MongoEntityMetadata<?> metadata = queryMethod.getEntityInformation();
assertThat(metadata.getJavaType(), is(typeCompatibleWith(Address.class)));
@@ -70,10 +69,7 @@ public class MongoQueryMethodUnitTests {
@Test
public void detectsCollectionFromReturnTypeIfReturnTypeAssignable() throws Exception {
Method method = SampleRepository2.class.getMethod("method");
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
context);
MongoQueryMethod queryMethod = queryMethod(SampleRepository2.class, "method");
MongoEntityMetadata<?> entityInformation = queryMethod.getEntityInformation();
assertThat(entityInformation.getJavaType(), is(typeCompatibleWith(Person.class)));
@@ -83,34 +79,44 @@ public class MongoQueryMethodUnitTests {
@Test
public void discoversUserAsDomainTypeForGeoPageQueryMethod() throws Exception {
MongoQueryMethod queryMethod = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class);
MongoQueryMethod queryMethod = queryMethod(PersonRepository.class, "findByLocationNear", Point.class,
Distance.class, Pageable.class);
assertThat(queryMethod.isGeoNearQuery(), is(true));
assertThat(queryMethod.isPageQuery(), is(true));
queryMethod = queryMethod("findByFirstname", String.class, Point.class);
queryMethod = queryMethod(PersonRepository.class, "findByFirstname", String.class, Point.class);
assertThat(queryMethod.isGeoNearQuery(), is(true));
assertThat(queryMethod.isPageQuery(), is(false));
assertThat(queryMethod.getEntityInformation().getJavaType(), is(typeCompatibleWith(User.class)));
assertThat(queryMethod("findByEmailAddress", String.class, Point.class).isGeoNearQuery(), is(true));
assertThat(queryMethod("findByFirstname", String.class, Point.class).isGeoNearQuery(), is(true));
assertThat(queryMethod("findByLastname", String.class, Point.class).isGeoNearQuery(), is(true));
assertThat(queryMethod(PersonRepository.class, "findByEmailAddress", String.class, Point.class).isGeoNearQuery(),
is(true));
assertThat(queryMethod(PersonRepository.class, "findByFirstname", String.class, Point.class).isGeoNearQuery(),
is(true));
assertThat(queryMethod(PersonRepository.class, "findByLastname", String.class, Point.class).isGeoNearQuery(),
is(true));
}
@Test(expected = IllegalArgumentException.class)
public void rejectsGeoPageQueryWithoutPageable() throws Exception {
queryMethod("findByLocationNear", Point.class, Distance.class);
queryMethod(PersonRepository.class, "findByLocationNear", Point.class, Distance.class);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsNullMappingContext() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class, Point.class);
new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), null);
new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class),
new SpelAwareProxyProjectionFactory(), null);
}
@Test
public void considersMethodReturningGeoPageAsPagingMethod() throws Exception {
MongoQueryMethod method = queryMethod("findByLocationNear", Point.class, Distance.class, Pageable.class);
MongoQueryMethod method = queryMethod(PersonRepository.class, "findByLocationNear", Point.class, Distance.class,
Pageable.class);
assertThat(method.isPageQuery(), is(true));
assertThat(method.isCollectionQuery(), is(false));
}
@@ -118,8 +124,7 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodObjectForMethodReturningAnInterface() throws Exception {
Method method = SampleRepository2.class.getMethod("methodReturningAnInterface");
new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository2.class), context);
queryMethod(SampleRepository2.class, "methodReturningAnInterface");
}
/**
@@ -128,7 +133,8 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodWithEmptyMetaCorrectly() throws Exception {
MongoQueryMethod method = queryMethod("emptyMetaAnnotation");
MongoQueryMethod method = queryMethod(PersonRepository.class, "emptyMetaAnnotation");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().hasValues(), is(false));
}
@@ -139,7 +145,8 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodWithMaxExecutionTimeCorrectly() throws Exception {
MongoQueryMethod method = queryMethod("metaWithMaxExecutionTime");
MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxExecutionTime");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().getMaxTimeMsec(), is(100L));
}
@@ -150,7 +157,8 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodWithMaxScanCorrectly() throws Exception {
MongoQueryMethod method = queryMethod("metaWithMaxScan");
MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithMaxScan");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().getMaxScan(), is(10L));
}
@@ -161,7 +169,8 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodWithCommentCorrectly() throws Exception {
MongoQueryMethod method = queryMethod("metaWithComment");
MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithComment");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().getComment(), is("foo bar"));
}
@@ -172,7 +181,8 @@ public class MongoQueryMethodUnitTests {
@Test
public void createsMongoQueryMethodWithSnapshotCorrectly() throws Exception {
MongoQueryMethod method = queryMethod("metaWithSnapshotUsage");
MongoQueryMethod method = queryMethod(PersonRepository.class, "metaWithSnapshotUsage");
assertThat(method.hasQueryMetaAttributes(), is(true));
assertThat(method.getQueryMetaAttributes().getSnapshot(), is(true));
}
@@ -183,14 +193,16 @@ public class MongoQueryMethodUnitTests {
@Test
public void fallsBackToRepositoryDomainTypeIfMethodDoesNotReturnADomainType() throws Exception {
MongoQueryMethod method = queryMethod("deleteByUserName", String.class);
MongoQueryMethod method = queryMethod(PersonRepository.class, "deleteByUserName", String.class);
assertThat(method.getEntityInformation().getJavaType(), is(typeCompatibleWith(User.class)));
}
private MongoQueryMethod queryMethod(String name, Class<?>... parameters) throws Exception {
Method method = PersonRepository.class.getMethod(name, parameters);
return new MongoQueryMethod(method, new DefaultRepositoryMetadata(PersonRepository.class), context);
private MongoQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
Method method = repository.getMethod(name, parameters);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
return new MongoQueryMethod(method, new DefaultRepositoryMetadata(repository), factory, context);
}
interface PersonRepository extends Repository<User, Long> {

View File

@@ -27,7 +27,6 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.mongodb.MongoDbFactory;
@@ -42,9 +41,12 @@ import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBObject;
import com.mongodb.util.JSONParseException;
/**
@@ -57,7 +59,6 @@ import com.mongodb.util.JSONParseException;
@RunWith(MockitoJUnitRunner.class)
public class PartTreeMongoQueryUnitTests {
@Mock RepositoryMetadata metadataMock;
@Mock MongoOperations mongoOperationsMock;
MongoMappingContext mappingContext;
@@ -65,11 +66,8 @@ public class PartTreeMongoQueryUnitTests {
public @Rule ExpectedException exception = ExpectedException.none();
@Before
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setUp() {
when(metadataMock.getDomainType()).thenReturn((Class) Person.class);
when(metadataMock.getReturnedDomainClass(Matchers.any(Method.class))).thenReturn((Class) Person.class);
mappingContext = new MongoMappingContext();
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mock(MongoDbFactory.class));
MongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
@@ -149,7 +147,52 @@ public class PartTreeMongoQueryUnitTests {
deriveQueryFromMethod("findByAge", new Object[] { 1 });
}
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object[] args) {
/**
* @see DATAMONGO-1345
*/
@Test
public void doesNotDeriveFieldSpecForNormalDomainType() {
assertThat(deriveQueryFromMethod("findPersonBy", new Object[0]).getFieldsObject(), is(nullValue()));
}
/**
* @see DATAMONGO-1345
*/
@Test
public void restrictsQueryToFieldsRequiredForProjection() {
DBObject fieldsObject = deriveQueryFromMethod("findPersonProjectedBy", new Object[0]).getFieldsObject();
assertThat(fieldsObject.get("firstname"), is((Object) 1));
assertThat(fieldsObject.get("lastname"), is((Object) 1));
}
/**
* @see DATAMONGO-1345
*/
@Test
public void restrictsQueryToFieldsRequiredForDto() {
DBObject fieldsObject = deriveQueryFromMethod("findPersonDtoBy", new Object[0]).getFieldsObject();
assertThat(fieldsObject.get("firstname"), is((Object) 1));
assertThat(fieldsObject.get("lastname"), is((Object) 1));
}
/**
* @see DATAMONGO-1345
*/
@Test
public void usesDynamicProjection() {
DBObject fields = deriveQueryFromMethod("findDynamicallyProjectedBy", ExtendedProjection.class).getFieldsObject();
assertThat(fields.get("firstname"), is((Object) 1));
assertThat(fields.get("lastname"), is((Object) 1));
assertThat(fields.get("age"), is((Object) 1));
}
private org.springframework.data.mongodb.core.query.Query deriveQueryFromMethod(String method, Object... args) {
Class<?>[] types = new Class<?>[args.length];
@@ -168,7 +211,9 @@ public class PartTreeMongoQueryUnitTests {
try {
Method method = Repo.class.getMethod(methodName, paramTypes);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadataMock, mappingContext);
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(Repo.class), factory,
mappingContext);
return new PartTreeMongoQuery(queryMethod, mongoOperationsMock);
} catch (NoSuchMethodException e) {
@@ -196,5 +241,36 @@ public class PartTreeMongoQueryUnitTests {
@Query(fields = "{ 'firstname }")
Person findByAge(Integer age);
Person findPersonBy();
PersonProjection findPersonProjectedBy();
PersonDto findPersonDtoBy();
<T> T findDynamicallyProjectedBy(Class<T> type);
}
interface PersonProjection {
String getFirstname();
String getLastname();
}
interface ExtendedProjection extends PersonProjection {
int getAge();
}
static class PersonDto {
public String firstname, lastname;
public PersonDto(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
}
}

View File

@@ -43,7 +43,10 @@ import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.Address;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -65,7 +68,6 @@ public class StringBasedMongoQueryUnitTests {
SpelExpressionParser PARSER = new SpelExpressionParser();
@Mock MongoOperations operations;
@Mock RepositoryMetadata metadata;
@Mock DbRefResolver factory;
MongoConverter converter;
@@ -81,10 +83,7 @@ public class StringBasedMongoQueryUnitTests {
@Test
public void bindsSimplePropertyCorrectly() throws Exception {
Method method = SampleRepository.class.getMethod("findByLastname", String.class);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext());
StringBasedMongoQuery mongoQuery = new StringBasedMongoQuery(queryMethod, operations, PARSER,
DefaultEvaluationContextProvider.INSTANCE);
StringBasedMongoQuery mongoQuery = createQueryForMethod("findByLastname", String.class);
ConvertingParameterAccessor accesor = StubParameterAccessor.getAccessor(converter, "Matthews");
org.springframework.data.mongodb.core.query.Query query = mongoQuery.createQuery(accesor);
@@ -366,11 +365,13 @@ public class StringBasedMongoQueryUnitTests {
private StringBasedMongoQuery createQueryForMethod(String name, Class<?>... parameters) throws Exception {
Method method = SampleRepository.class.getMethod(name, parameters);
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, converter.getMappingContext());
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
MongoQueryMethod queryMethod = new MongoQueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
factory, converter.getMappingContext());
return new StringBasedMongoQuery(queryMethod, operations, PARSER, DefaultEvaluationContextProvider.INSTANCE);
}
private interface SampleRepository {
private interface SampleRepository extends Repository<Person, Long> {
@Query("{ 'lastname' : ?0 }")
Person findByLastname(String lastname);

View File

@@ -130,7 +130,7 @@ class StubParameterAccessor implements MongoParameterAccessor {
public TextCriteria getFullText() {
return null;
}
/* (non-Javadoc)
* @see org.springframework.data.mongodb.repository.query.MongoParameterAccessor#getValues()
*/
@@ -138,4 +138,13 @@ class StubParameterAccessor implements MongoParameterAccessor {
public Object[] getValues() {
return this.values;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.ParameterAccessor#getDynamicProjection()
*/
@Override
public Class<?> getDynamicProjection() {
return null;
}
}