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:
committed by
Oliver Gierke
parent
e00b98452d
commit
69d507f546
@@ -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.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -33,11 +35,13 @@ import org.springframework.util.Assert;
|
||||
* with specific information that is necessary to construct {@link RepositoryQuery}s for the method.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Thomas Darimont
|
||||
*/
|
||||
public class QueryMethod {
|
||||
|
||||
private final RepositoryMetadata metadata;
|
||||
private final Method method;
|
||||
private final Class<?> unwrappedReturnType;
|
||||
private final Parameters<?, ?> parameters;
|
||||
|
||||
private Class<?> domainClass;
|
||||
@@ -72,6 +76,7 @@ public class QueryMethod {
|
||||
this.method = method;
|
||||
this.parameters = createParameters(method);
|
||||
this.metadata = metadata;
|
||||
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(method);
|
||||
|
||||
Assert.notNull(this.parameters);
|
||||
|
||||
@@ -157,9 +162,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,9 +174,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,9 +183,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,4 +221,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ 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;
|
||||
import org.springframework.data.domain.Page;
|
||||
@@ -33,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 {
|
||||
|
||||
@@ -138,6 +142,30 @@ public class QueryMethodUnitTests {
|
||||
assertThat(new QueryMethod(method, repositoryMetadata).isCollectionQuery(), 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);
|
||||
@@ -157,6 +185,20 @@ public class QueryMethodUnitTests {
|
||||
Slice<User> sliceOfUsers();
|
||||
|
||||
User[] arrayOfUsers();
|
||||
|
||||
Stream<String> streaming();
|
||||
|
||||
Stream<String> streaming(Pageable pageable);
|
||||
|
||||
/**
|
||||
* @see DATACMNS-716
|
||||
*/
|
||||
Future<User> returnsFutureForSingleEntity();
|
||||
|
||||
/**
|
||||
* @see DATACMNS-716
|
||||
*/
|
||||
Future<List<User>> returnsFutureForEntityCollection();
|
||||
}
|
||||
|
||||
class User {
|
||||
|
||||
Reference in New Issue
Block a user