DATACMNS-1802 - Add support for suspend repository query methods returning List<T>.
We now support returning List<T> from a suspended repository query method to simplify consumption of collection queries.
This commit is contained in:
@@ -70,12 +70,14 @@ interface CoroutineRepository : CoroutineCrudRepository<User, String> {
|
|||||||
suspend fun findOne(id: String): User
|
suspend fun findOne(id: String): User
|
||||||
|
|
||||||
fun findByFirstname(firstname: String): Flow<User>
|
fun findByFirstname(firstname: String): Flow<User>
|
||||||
|
|
||||||
|
suspend fun findAllByFirstname(id: String): List<User>
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
Coroutines repositories are built on reactive repositories to expose the non-blocking nature of data access through Kotlin's Coroutines.
|
Coroutines repositories are built on reactive repositories to expose the non-blocking nature of data access through Kotlin's Coroutines.
|
||||||
Methods on a Coroutines repository can be backed either by a query method or a custom implementation.
|
Methods on a Coroutines repository can be backed either by a query method or a custom implementation.
|
||||||
Invoking a custom implementation method propagates the Coroutines invocation to the actual implementation method if the custom method is `suspend`able without requiring the implementation method to return a reactive type such as `Mono` or `Flux`.
|
Invoking a custom implementation method propagates the Coroutines invocation to the actual implementation method if the custom method is `suspend`-able without requiring the implementation method to return a reactive type such as `Mono` or `Flux`.
|
||||||
|
|
||||||
NOTE: Coroutines repositories are only discovered when the repository extends the `CoroutineCrudRepository` interface.
|
NOTE: Coroutines repositories are only discovered when the repository extends the `CoroutineCrudRepository` interface.
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import org.reactivestreams.Publisher;
|
import org.reactivestreams.Publisher;
|
||||||
|
|
||||||
import org.springframework.core.KotlinDetector;
|
import org.springframework.core.KotlinDetector;
|
||||||
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
|
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
|
||||||
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult;
|
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult;
|
||||||
@@ -176,6 +178,10 @@ abstract class RepositoryMethodInvoker {
|
|||||||
return ReactiveWrapperConverters.toWrapper(result, returnedType);
|
return ReactiveWrapperConverters.toWrapper(result, returnedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Collection.class.isAssignableFrom(returnedType)) {
|
||||||
|
result = Flux.from(result).collectList();
|
||||||
|
}
|
||||||
|
|
||||||
return AwaitKt.awaitFirstOrNull(result, continuation);
|
return AwaitKt.awaitFirstOrNull(result, continuation);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));
|
multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));
|
||||||
@@ -188,8 +194,6 @@ abstract class RepositoryMethodInvoker {
|
|||||||
captured.getDuration());
|
captured.getDuration());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface Invokable {
|
interface Invokable {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class QueryMethod {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(method);
|
this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(metadata, method);
|
||||||
this.parameters = createParameters(method);
|
this.parameters = createParameters(method);
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ public class QueryMethod {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> returnType = method.getReturnType();
|
Class<?> returnType = metadata.getReturnType(method).getType();
|
||||||
|
|
||||||
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
|
if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -268,13 +268,14 @@ public class QueryMethod {
|
|||||||
return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike();
|
return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(Method method) {
|
private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(RepositoryMetadata metadata, Method method) {
|
||||||
|
|
||||||
if (QueryExecutionConverters.supports(method.getReturnType())) {
|
TypeInformation<?> returnType = metadata.getReturnType(method);
|
||||||
|
if (QueryExecutionConverters.supports(returnType.getType())) {
|
||||||
|
|
||||||
// unwrap only one level to handle cases like Future<List<Entity>> correctly.
|
// unwrap only one level to handle cases like Future<List<Entity>> correctly.
|
||||||
|
|
||||||
TypeInformation<?> componentType = ClassTypeInformation.fromReturnTypeOf(method).getComponentType();
|
TypeInformation<?> componentType = returnType.getComponentType();
|
||||||
|
|
||||||
if (componentType == null) {
|
if (componentType == null) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
@@ -284,7 +285,7 @@ public class QueryMethod {
|
|||||||
return componentType.getType();
|
return componentType.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
return method.getReturnType();
|
return returnType.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertReturnTypeAssignable(Method method, Set<Class<?>> types) {
|
private static void assertReturnTypeAssignable(Method method, Set<Class<?>> types) {
|
||||||
|
|||||||
@@ -266,6 +266,26 @@ class CoroutineCrudRepositoryUnitTests {
|
|||||||
assertThat(emptyResult).isEmpty()
|
assertThat(emptyResult).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // DATACMNS-1802
|
||||||
|
fun shouldBridgeSuspendedAsListMethod() {
|
||||||
|
|
||||||
|
val sample = User()
|
||||||
|
|
||||||
|
Mockito.`when`(factory.queryOne.execute(arrayOf("foo", null))).thenReturn(Flux.just(sample), Flux.empty<User>())
|
||||||
|
|
||||||
|
val result = runBlocking {
|
||||||
|
coRepository.findSuspendedAsList("foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(result).hasSize(1).containsOnly(sample)
|
||||||
|
|
||||||
|
val emptyResult = runBlocking {
|
||||||
|
coRepository.findSuspendedAsList("foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(emptyResult).isEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
interface MyCoRepository : CoroutineCrudRepository<User, String> {
|
interface MyCoRepository : CoroutineCrudRepository<User, String> {
|
||||||
|
|
||||||
suspend fun findOne(id: String): User
|
suspend fun findOne(id: String): User
|
||||||
@@ -273,5 +293,7 @@ class CoroutineCrudRepositoryUnitTests {
|
|||||||
fun findMultiple(id: String): Flow<User>
|
fun findMultiple(id: String): Flow<User>
|
||||||
|
|
||||||
suspend fun findSuspendedMultiple(id: String): Flow<User>
|
suspend fun findSuspendedMultiple(id: String): Flow<User>
|
||||||
|
|
||||||
|
suspend fun findSuspendedAsList(id: String): List<User>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user