DATACMNS-716 - Allow collection executions with wrapper types as return values.

We now support collection executions in query derivation in combination with wrapper types like (Future, Optional) that wrap a collection, e.g. Future<List<User>>. Previously we used the "raw" return type which tricked our detection for Collection / Page / Slice executions.

Original pull request: #130.
This commit is contained in:
Thomas Darimont
2015-06-17 17:21:53 +02:00
committed by Oliver Gierke
parent 278aefc756
commit 5de42f25cc
2 changed files with 56 additions and 9 deletions

View File

@@ -26,6 +26,8 @@ import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.EntityMetadata;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.util.Assert;
@@ -40,6 +42,7 @@ public class QueryMethod {
private final RepositoryMetadata metadata;
private final Method method;
private final Class<?> unwrappedReturnType;
private final Parameters<?, ?> parameters;
private Class<?> domainClass;
@@ -64,6 +67,7 @@ public class QueryMethod {
}
this.method = method;
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(method);
this.parameters = createParameters(method);
this.metadata = metadata;
@@ -163,9 +167,9 @@ public class QueryMethod {
*/
public boolean isCollectionQuery() {
Class<?> returnType = method.getReturnType();
return !(isPageQuery() || isSliceQuery())
&& org.springframework.util.ClassUtils.isAssignable(Iterable.class, returnType) || returnType.isArray();
&& org.springframework.util.ClassUtils.isAssignable(Iterable.class, unwrappedReturnType)
|| unwrappedReturnType.isArray();
}
/**
@@ -175,9 +179,7 @@ public class QueryMethod {
* @since 1.8
*/
public boolean isSliceQuery() {
Class<?> returnType = method.getReturnType();
return !isPageQuery() && org.springframework.util.ClassUtils.isAssignable(Slice.class, returnType);
return !isPageQuery() && org.springframework.util.ClassUtils.isAssignable(Slice.class, unwrappedReturnType);
}
/**
@@ -186,9 +188,7 @@ public class QueryMethod {
* @return
*/
public final boolean isPageQuery() {
Class<?> returnType = method.getReturnType();
return org.springframework.util.ClassUtils.isAssignable(Page.class, returnType);
return org.springframework.util.ClassUtils.isAssignable(Page.class, unwrappedReturnType);
}
/**
@@ -216,7 +216,7 @@ public class QueryMethod {
* @since 1.10
*/
public boolean isStreamQuery() {
return ReflectionUtils.isJava8StreamType(method.getReturnType());
return ReflectionUtils.isJava8StreamType(unwrappedReturnType);
}
/**
@@ -236,4 +236,14 @@ public class QueryMethod {
public String toString() {
return method.toString();
}
private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(Method method) {
if (QueryExecutionConverters.supports(method.getReturnType())) {
// unwrap only one level to handle cases like Future<List<Entity>> correctly.
return ClassTypeInformation.fromReturnTypeOf(method).getComponentType().getType();
}
return method.getReturnType();
}
}

View File

@@ -20,6 +20,8 @@ import static org.junit.Assert.*;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.Future;
import java.util.stream.Stream;
import org.junit.Test;
@@ -34,6 +36,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
* Unit tests for {@link QueryMethod}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class QueryMethodUnitTests {
@@ -163,6 +166,30 @@ public class QueryMethodUnitTests {
assertThat(new QueryMethod(method, repositoryMetadata).isStreamQuery(), is(true));
}
/**
* @see DATACMNS-716
*/
@Test
public void doesNotRejectFutureQueryForSingleEntity() throws Exception {
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
Method method = SampleRepository.class.getMethod("returnsFutureForSingleEntity");
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(false));
}
/**
* @see DATACMNS-716
*/
@Test
public void doesNotRejectFutureQueryForEntityCollection() throws Exception {
RepositoryMetadata repositoryMetadata = new DefaultRepositoryMetadata(SampleRepository.class);
Method method = SampleRepository.class.getMethod("returnsFutureForEntityCollection");
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), is(true));
}
interface SampleRepository extends Repository<User, Serializable> {
String pagingMethodWithInvalidReturnType(Pageable pageable);
@@ -186,6 +213,16 @@ public class QueryMethodUnitTests {
Stream<String> streaming();
Stream<String> streaming(Pageable pageable);
/**
* @see DATACMNS-716
*/
Future<User> returnsFutureForSingleEntity();
/**
* @see DATACMNS-716
*/
Future<List<User>> returnsFutureForEntityCollection();
}
class User {