DATACMNS-89 - Infrastructure for projections on repository queries.
QueryMethods now expose a ResourceProcessor which is exposes information about the final to be created object types which can either be DTOs containing a persistence constructor (see @PersistenceConstructor) or projection interfaces. The former are analyzed for constructor properties so that store implementations can use that information to create projected queries that return exactly the fields required for that DTO. Projection interfaces are inspected, their properties are considered input properties and the same projection queries can be issued against the data store. If a projection contains dynamically calculated properties (i.e. it uses SpEL expressions via @Value) the original entities have to be queried and can be projected during post processing. ProjectionFactory now exposes a more advanced ProjectionInformation that has additional meta information about the projection type. ProxyProjectionFactory now refers to the BeanClassLoader instead of the ResourceLoader. RepositoryFactory(Bean)Support now also implement BeanFactoryAware to forward the BeanFactory to the SpelAwareProxyProjectionFactory which in turn now gets handed into the QueryLookupStrategy as well as the QueryMethod. Parameter now knows about a dynamic projection type, which is a query method parameter of type Class bound to a generic method parameter and will be used to determine the projection to be used on a per call basis. Original pull request: #150.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.projection;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultProjectionInformation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultProjectionInformationUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void discoversInputProperties() {
|
||||
|
||||
ProjectionInformation information = new DefaultProjectionInformation(CustomerProjection.class);
|
||||
|
||||
assertThat(toNames(information.getInputProperties()), contains("firstname", "lastname"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void discoversAllInputProperties() {
|
||||
|
||||
ProjectionInformation information = new DefaultProjectionInformation(ExtendedProjection.class);
|
||||
|
||||
assertThat(toNames(information.getInputProperties()), hasItems("age", "firstname", "lastname"));
|
||||
}
|
||||
|
||||
private static List<String> toNames(List<PropertyDescriptor> descriptors) {
|
||||
|
||||
List<String> names = new ArrayList<String>(descriptors.size());
|
||||
|
||||
for (PropertyDescriptor descriptor : descriptors) {
|
||||
names.add(descriptor.getName());
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
interface CustomerProjection {
|
||||
|
||||
String getFirstname();
|
||||
|
||||
String getLastname();
|
||||
}
|
||||
|
||||
interface ExtendedProjection extends CustomerProjection {
|
||||
|
||||
int getAge();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.data.projection;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -155,10 +156,10 @@ public class ProxyProjectionFactoryUnitTests {
|
||||
@Test
|
||||
public void returnsAllPropertiesAsInputProperties() {
|
||||
|
||||
List<String> result = factory.getInputProperties(CustomerExcerpt.class);
|
||||
ProjectionInformation projectionInformation = factory.getProjectionInformation(CustomerExcerpt.class);
|
||||
List<PropertyDescriptor> result = projectionInformation.getInputProperties();
|
||||
|
||||
assertThat(result, hasSize(5));
|
||||
assertThat(result, hasItems("firstname", "address", "shippingAddresses", "picture"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,6 +235,18 @@ public class ProxyProjectionFactoryUnitTests {
|
||||
assertThat(excerpt.getId(), is(customer.id.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void exposesProjectionInformationCorrectly() {
|
||||
|
||||
ProjectionInformation information = factory.getProjectionInformation(CustomerExcerpt.class);
|
||||
|
||||
assertThat(information.getType(), is(typeCompatibleWith(CustomerExcerpt.class)));
|
||||
assertThat(information.isClosed(), is(true));
|
||||
}
|
||||
|
||||
static class Customer {
|
||||
|
||||
public Long id;
|
||||
|
||||
@@ -65,6 +65,17 @@ public class SpelAwareProxyProjectionFactoryUnitTests {
|
||||
assertThat(properties, hasItem("firstname"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void considersProjectionUsingAtValueNotClosed() {
|
||||
|
||||
ProjectionInformation information = factory.getProjectionInformation(CustomerExcerpt.class);
|
||||
|
||||
assertThat(information.isClosed(), is(false));
|
||||
}
|
||||
|
||||
static class Customer {
|
||||
|
||||
public String firstname, lastname;
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
@@ -48,9 +49,8 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
this.repository = repository;
|
||||
|
||||
when(
|
||||
strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
|
||||
Mockito.any(NamedQueries.class))).thenReturn(queryOne);
|
||||
when(strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
|
||||
Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2011 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.
|
||||
@@ -26,6 +26,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
@@ -40,12 +41,9 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class QueryExecutorMethodInterceptorUnitTests {
|
||||
|
||||
@Mock
|
||||
RepositoryFactorySupport factory;
|
||||
@Mock
|
||||
RepositoryInformation information;
|
||||
@Mock
|
||||
QueryLookupStrategy strategy;
|
||||
@Mock RepositoryFactorySupport factory;
|
||||
@Mock RepositoryInformation information;
|
||||
@Mock QueryLookupStrategy strategy;
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsRepositoryInterfaceWithQueryMethodsIfNoQueryLookupStrategyIsDefined() throws Exception {
|
||||
@@ -63,6 +61,7 @@ public class QueryExecutorMethodInterceptorUnitTests {
|
||||
when(factory.getQueryLookupStrategy(any(Key.class))).thenReturn(strategy);
|
||||
|
||||
factory.new QueryExecutorMethodInterceptor(information, null, new Object());
|
||||
verify(strategy, times(0)).resolveQuery(any(Method.class), any(RepositoryMetadata.class), any(NamedQueries.class));
|
||||
verify(strategy, times(0)).resolveQuery(any(Method.class), any(RepositoryMetadata.class),
|
||||
any(ProjectionFactory.class), any(NamedQueries.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.RepositoryDefinition;
|
||||
@@ -94,7 +95,8 @@ public class RepositoryFactorySupportUnitTests {
|
||||
Mockito.reset(factory.strategy);
|
||||
|
||||
when(factory.strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
|
||||
Mockito.any(NamedQueries.class))).thenReturn(factory.queryOne, factory.queryTwo);
|
||||
Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(factory.queryOne,
|
||||
factory.queryTwo);
|
||||
|
||||
factory.addQueryCreationListener(listener);
|
||||
factory.addQueryCreationListener(otherListener);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2014 the original author or authors.
|
||||
* Copyright 2008-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. You may obtain a copy of
|
||||
@@ -151,7 +151,20 @@ public class ParametersUnitTests {
|
||||
assertThat(parameter.isExplicitlyNamed(), is(false));
|
||||
}
|
||||
|
||||
private Parameters<?, ?> getParametersFor(String methodName, Class<?>... parameterTypes)
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsDynamicProjectionParameter() throws Exception {
|
||||
|
||||
Parameters<?, Parameter> parameters = getParametersFor("dynamicBind", Class.class, Class.class, Class.class);
|
||||
|
||||
assertThat(parameters.getParameter(0).isDynamicProjectionParameter(), is(true));
|
||||
assertThat(parameters.getParameter(1).isDynamicProjectionParameter(), is(false));
|
||||
assertThat(parameters.getParameter(2).isDynamicProjectionParameter(), is(false));
|
||||
}
|
||||
|
||||
private Parameters<?, Parameter> getParametersFor(String methodName, Class<?>... parameterTypes)
|
||||
throws SecurityException, NoSuchMethodException {
|
||||
|
||||
Method method = SampleDao.class.getMethod(methodName, parameterTypes);
|
||||
@@ -181,5 +194,6 @@ public class ParametersUnitTests {
|
||||
|
||||
User emptyParameters();
|
||||
|
||||
<T> T dynamicBind(Class<T> type, Class<?> one, Class<Object> two);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.springframework.core.SpringVersion;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
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;
|
||||
@@ -48,6 +50,7 @@ public class QueryMethodUnitTests {
|
||||
private static final Version FOUR_DOT_TWO = new Version(4, 2);
|
||||
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
ProjectionFactory factory = new SpelAwareProxyProjectionFactory();
|
||||
|
||||
/**
|
||||
* @see DATAJPA-59
|
||||
@@ -56,7 +59,7 @@ public class QueryMethodUnitTests {
|
||||
public void rejectsPagingMethodWithInvalidReturnType() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("pagingMethodWithInvalidReturnType", Pageable.class);
|
||||
new QueryMethod(method, metadata);
|
||||
new QueryMethod(method, metadata, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +68,7 @@ public class QueryMethodUnitTests {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsPagingMethodWithoutPageable() throws Exception {
|
||||
Method method = SampleRepository.class.getMethod("pagingMethodWithoutPageable");
|
||||
new QueryMethod(method, metadata);
|
||||
new QueryMethod(method, metadata, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +77,7 @@ public class QueryMethodUnitTests {
|
||||
@Test
|
||||
public void setsUpSimpleQueryMethodCorrectly() throws Exception {
|
||||
Method method = SampleRepository.class.getMethod("findByUsername", String.class);
|
||||
new QueryMethod(method, metadata);
|
||||
new QueryMethod(method, metadata, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +86,7 @@ public class QueryMethodUnitTests {
|
||||
@Test
|
||||
public void considersIterableMethodForCollectionQuery() throws Exception {
|
||||
Method method = SampleRepository.class.getMethod("sampleMethod");
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata);
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||
assertThat(queryMethod.isCollectionQuery(), is(true));
|
||||
}
|
||||
|
||||
@@ -93,7 +96,7 @@ public class QueryMethodUnitTests {
|
||||
@Test
|
||||
public void doesNotConsiderPageMethodCollectionQuery() throws Exception {
|
||||
Method method = SampleRepository.class.getMethod("anotherSampleMethod", Pageable.class);
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata);
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||
assertThat(queryMethod.isPageQuery(), is(true));
|
||||
assertThat(queryMethod.isCollectionQuery(), is(false));
|
||||
}
|
||||
@@ -105,7 +108,7 @@ public class QueryMethodUnitTests {
|
||||
public void detectsAnEntityBeingReturned() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("returnsEntitySubclass");
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata);
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||
|
||||
assertThat(queryMethod.isQueryForEntity(), is(true));
|
||||
}
|
||||
@@ -117,7 +120,7 @@ public class QueryMethodUnitTests {
|
||||
public void detectsNonEntityBeingReturned() throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod("returnsProjection");
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata);
|
||||
QueryMethod queryMethod = new QueryMethod(method, metadata, factory);
|
||||
|
||||
assertThat(queryMethod.isQueryForEntity(), is(false));
|
||||
}
|
||||
@@ -130,7 +133,7 @@ public class QueryMethodUnitTests {
|
||||
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("sliceOfUsers");
|
||||
QueryMethod queryMethod = new QueryMethod(method, repositoryMetadata);
|
||||
QueryMethod queryMethod = new QueryMethod(method, repositoryMetadata, factory);
|
||||
|
||||
assertThat(queryMethod.isSliceQuery(), is(true));
|
||||
assertThat(queryMethod.isCollectionQuery(), is(false));
|
||||
@@ -146,7 +149,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("arrayOfUsers");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(true));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +161,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("streaming");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isStreamQuery(), is(true));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isStreamQuery(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +173,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("streaming", Pageable.class);
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isStreamQuery(), is(true));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isStreamQuery(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,7 +185,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("returnsCompletableFutureForSingleEntity");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(false));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +199,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("returnsCompletableFutureForEntityCollection");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(true));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +211,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("returnsFutureForSingleEntity");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(false));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +223,7 @@ public class QueryMethodUnitTests {
|
||||
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
|
||||
Method method = SampleRepository.class.getMethod("returnsFutureForEntityCollection");
|
||||
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(true));
|
||||
assertThat(new QueryMethod(method, repositoryMetadata, factory).isCollectionQuery(), is(true));
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<User, Serializable> {
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ResultProcessorUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void leavesNonProjectingResultUntouched() throws Exception {
|
||||
|
||||
ResultProcessor information = new ResultProcessor(getQueryMethod("findAll"), new SpelAwareProxyProjectionFactory());
|
||||
|
||||
Sample sample = new Sample("Dave", "Matthews");
|
||||
List<Sample> result = new ArrayList<Sample>(Arrays.asList(sample));
|
||||
List<Sample> converted = information.processResult(result);
|
||||
|
||||
assertThat(converted, contains(sample));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void createsProjectionFromProperties() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findOneProjection");
|
||||
|
||||
SampleProjection result = information.processResult(Arrays.asList("Matthews"));
|
||||
|
||||
assertThat(result.getLastname(), is("Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void createsListOfProjectionsFormNestedLists() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findAllProjection");
|
||||
|
||||
List<String> columns = Arrays.asList("Matthews");
|
||||
List<List<String>> source = new ArrayList<List<String>>(Arrays.asList(columns));
|
||||
|
||||
List<SampleProjection> result = information.processResult(source);
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result.get(0).getLastname(), is("Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void createsListOfProjectionsFromMaps() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findAllProjection");
|
||||
|
||||
List<Map<String, Object>> source = new ArrayList<Map<String, Object>>(
|
||||
Arrays.asList(Collections.<String, Object> singletonMap("lastname", "Matthews")));
|
||||
|
||||
List<SampleProjection> result = information.processResult(source);
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result.get(0).getLastname(), is("Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void createsListOfProjectionsFromEntity() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findAllProjection");
|
||||
|
||||
List<Sample> source = new ArrayList<Sample>(Arrays.asList(new Sample("Dave", "Matthews")));
|
||||
List<SampleProjection> result = information.processResult(source);
|
||||
|
||||
assertThat(result, hasSize(1));
|
||||
assertThat(result.get(0).getLastname(), is("Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void createsPageOfProjectionsFromEntity() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findPageProjection", Pageable.class);
|
||||
|
||||
Page<Sample> source = new PageImpl<Sample>(Arrays.asList(new Sample("Dave", "Matthews")));
|
||||
Page<SampleProjection> result = information.processResult(source);
|
||||
|
||||
assertThat(result.getContent(), hasSize(1));
|
||||
assertThat(result.getContent().get(0).getLastname(), is("Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void createsDynamicProjectionFromEntity() throws Exception {
|
||||
|
||||
ResultProcessor information = getFactory("findOneOpenProjection");
|
||||
|
||||
OpenProjection result = information.processResult(new Sample("Dave", "Matthews"));
|
||||
|
||||
assertThat(result.getLastname(), is("Matthews"));
|
||||
assertThat(result.getFullName(), is("Dave Matthews"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void findsDynamicProjection() throws Exception {
|
||||
|
||||
ParameterAccessor accessor = mock(ParameterAccessor.class);
|
||||
|
||||
ResultProcessor factory = getFactory("findOneDynamic", Class.class);
|
||||
assertThat(factory.withDynamicProjection(null), is(factory));
|
||||
assertThat(factory.withDynamicProjection(accessor), is(factory));
|
||||
|
||||
doReturn(SampleProjection.class).when(accessor).getDynamicProjection();
|
||||
|
||||
ResultProcessor processor = factory.withDynamicProjection(accessor);
|
||||
assertThat(processor.getReturnedType().getReturnedType(), is(typeCompatibleWith(SampleProjection.class)));
|
||||
}
|
||||
|
||||
private static ResultProcessor getFactory(String methodName, Class<?>... parameters) throws Exception {
|
||||
return getQueryMethod(methodName, parameters).getResultProcessor();
|
||||
}
|
||||
|
||||
private static QueryMethod getQueryMethod(String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new QueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory());
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<Sample, Long> {
|
||||
|
||||
List<Sample> findAll();
|
||||
|
||||
List<SampleDTO> findAllDtos();
|
||||
|
||||
List<SampleProjection> findAllProjection();
|
||||
|
||||
Sample findOne();
|
||||
|
||||
SampleDTO findOneDto();
|
||||
|
||||
SampleProjection findOneProjection();
|
||||
|
||||
OpenProjection findOneOpenProjection();
|
||||
|
||||
Page<SampleProjection> findPageProjection(Pageable pageable);
|
||||
|
||||
<T> T findOneDynamic(Class<T> type);
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
public String firstname, lastname;
|
||||
|
||||
public Sample(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
}
|
||||
|
||||
static class SampleDTO {}
|
||||
|
||||
interface SampleProjection {
|
||||
|
||||
String getLastname();
|
||||
}
|
||||
|
||||
interface OpenProjection {
|
||||
|
||||
String getLastname();
|
||||
|
||||
@Value("#{target.firstname + ' ' + target.lastname}")
|
||||
String getFullName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ReturnedType}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ReturnedTypeUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void treatsSimpleDomainTypeAsIs() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("findAll");
|
||||
|
||||
assertThat(type.getTypeToRead(), is(typeCompatibleWith(Sample.class)));
|
||||
assertThat(type.getInputProperties(), is(empty()));
|
||||
assertThat(type.isProjecting(), is(false));
|
||||
assertThat(type.needsCustomConstruction(), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsDto() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("findAllDtos");
|
||||
|
||||
assertThat(type.getTypeToRead(), is(typeCompatibleWith(SampleDto.class)));
|
||||
assertThat(type.getInputProperties(), contains("firstname"));
|
||||
assertThat(type.isInstance(new SampleDto("firstname")), is(true));
|
||||
assertThat(type.isProjecting(), is(true));
|
||||
assertThat(type.needsCustomConstruction(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsProjection() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("findAllProjection");
|
||||
|
||||
assertThat(type.getTypeToRead(), is(nullValue()));
|
||||
assertThat(type.getInputProperties(), contains("lastname"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsVoidMethod() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("voidMethod");
|
||||
|
||||
assertThat(type.getDomainType(), is(typeCompatibleWith(Sample.class)));
|
||||
assertThat(type.getReturnedType(), is(typeCompatibleWith(void.class)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsClosedProjection() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("findOneProjection");
|
||||
|
||||
assertThat(type.getReturnedType(), is(typeCompatibleWith(SampleProjection.class)));
|
||||
assertThat(type.isProjecting(), is(true));
|
||||
assertThat(type.needsCustomConstruction(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-89
|
||||
*/
|
||||
@Test
|
||||
public void detectsOpenProjection() throws Exception {
|
||||
|
||||
ReturnedType type = getReturnedType("findOneOpenProjection");
|
||||
|
||||
assertThat(type.getReturnedType(), is(typeCompatibleWith(OpenProjection.class)));
|
||||
assertThat(type.isProjecting(), is(true));
|
||||
assertThat(type.needsCustomConstruction(), is(false));
|
||||
assertThat(type.getTypeToRead(), is(typeCompatibleWith(Sample.class)));
|
||||
}
|
||||
|
||||
private static ReturnedType getReturnedType(String methodName, Class<?>... parameters) throws Exception {
|
||||
return getQueryMethod(methodName, parameters).getResultProcessor().getReturnedType();
|
||||
}
|
||||
|
||||
private static QueryMethod getQueryMethod(String name, Class<?>... parameters) throws Exception {
|
||||
|
||||
Method method = SampleRepository.class.getMethod(name, parameters);
|
||||
return new QueryMethod(method, new DefaultRepositoryMetadata(SampleRepository.class),
|
||||
new SpelAwareProxyProjectionFactory());
|
||||
}
|
||||
|
||||
interface SampleRepository extends Repository<Sample, Long> {
|
||||
|
||||
void voidMethod();
|
||||
|
||||
List<Sample> findAll();
|
||||
|
||||
List<SampleDto> findAllDtos();
|
||||
|
||||
List<SampleProjection> findAllProjection();
|
||||
|
||||
Sample findOne();
|
||||
|
||||
SampleDto findOneDto();
|
||||
|
||||
SampleProjection findOneProjection();
|
||||
|
||||
OpenProjection findOneOpenProjection();
|
||||
|
||||
Page<SampleProjection> findPageProjection(Pageable pageable);
|
||||
}
|
||||
|
||||
static class Sample {
|
||||
public String firstname, lastname;
|
||||
|
||||
public Sample(String firstname, String lastname) {
|
||||
this.firstname = firstname;
|
||||
this.lastname = lastname;
|
||||
}
|
||||
}
|
||||
|
||||
static class SampleDto {
|
||||
|
||||
public SampleDto(String firstname) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
interface SampleProjection {
|
||||
|
||||
String getLastname();
|
||||
}
|
||||
|
||||
interface OpenProjection {
|
||||
|
||||
String getLastname();
|
||||
|
||||
@Value("#{target.firstname + ' ' + target.lastname}")
|
||||
String getFullName();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user