DATACMNS-1508 - Support for Kotlin Coroutines repositories.

We now support Kotlin Coroutines repositories backed by reactive repositories. Coroutine repositories can either invoke reactive query methods or implemented methods that are backed either by methods returning a reactive wrapper or that are native suspended functions.

Exclude Coroutines Continuation from bindable parameters and name discovery and do not unwrap results for suspended functions returning a reactive type.

interface CoroutinesRepository : CoroutineCrudRepository<User, String> {

    suspend fun findOne(id: String): User

    fun findByFirstname(firstname: String): Flow<User>
}

Original pull request: #415.
This commit is contained in:
Mark Paluch
2019-11-14 14:05:04 +01:00
parent 1d2b81bec8
commit ce7683c672
21 changed files with 1147 additions and 139 deletions

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2019 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
*
* https://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.core.support;
import static org.mockito.Mockito.*;
import java.lang.reflect.Method;
import java.util.Optional;
import org.mockito.Mockito;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.EntityInformation;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
/**
* @author Mark Paluch
*/
public class DummyReactiveRepositoryFactory extends ReactiveRepositoryFactorySupport {
public final DummyRepositoryFactory.MyRepositoryQuery queryOne = mock(DummyRepositoryFactory.MyRepositoryQuery.class);
public final RepositoryQuery queryTwo = mock(RepositoryQuery.class);
public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);
@SuppressWarnings("unchecked") private final QuerydslPredicateExecutor<Object> querydsl = mock(
QuerydslPredicateExecutor.class);
private final Object repository;
public DummyReactiveRepositoryFactory(Object repository) {
this.repository = repository;
when(strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)
*/
@Override
@SuppressWarnings("unchecked")
public <T, ID> EntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
return mock(EntityInformation.class);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Object getTargetRepository(RepositoryInformation information) {
return repository;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return repository.getClass();
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider)
*/
@Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return Optional.of(strategy);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryFragments(org.springframework.data.repository.core.RepositoryMetadata)
*/
@Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
return QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()) //
? fragments.append(RepositoryComposition.RepositoryFragments.just(querydsl)) //
: fragments;
}
}

View File

@@ -28,7 +28,6 @@ import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryFactorySupportUnitTests.MyRepositoryQuery;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
@@ -109,4 +108,11 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport {
? fragments.append(RepositoryFragments.just(querydsl)) //
: fragments;
}
/**
* @author Mark Paluch
*/
public static interface MyRepositoryQuery extends RepositoryQuery {
}
}

View File

@@ -19,6 +19,7 @@ import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.core.support.DummyRepositoryFactory.*;
import java.io.Serializable;
import java.lang.reflect.Method;
@@ -93,8 +94,7 @@ public class RepositoryFactorySupportUnitTests {
Mockito.reset(factory.strategy);
when(factory.strategy.resolveQuery(any(Method.class), any(RepositoryMetadata.class), any(ProjectionFactory.class),
any(NamedQueries.class))).thenReturn(factory.queryOne,
factory.queryTwo);
any(NamedQueries.class))).thenReturn(factory.queryOne, factory.queryTwo);
factory.addQueryCreationListener(listener);
factory.addQueryCreationListener(otherListener);
@@ -309,7 +309,7 @@ public class RepositoryFactorySupportUnitTests {
assertThatThrownBy( //
() -> repository.findById("")) //
.isInstanceOf(EmptyResultDataAccessException.class) //
.hasMessageContaining("Result must not be null!");
.hasMessageContaining("Result must not be null!");
assertThat(repository.findByUsername("")).isNull();
}
@@ -322,7 +322,7 @@ public class RepositoryFactorySupportUnitTests {
assertThatThrownBy( //
() -> repository.findByClass(null)) //
.isInstanceOf(IllegalArgumentException.class) //
.hasMessageContaining("must not be null!");
.hasMessageContaining("must not be null!");
}
@Test // DATACMNS-1154
@@ -427,10 +427,6 @@ public class RepositoryFactorySupportUnitTests {
}
interface MyRepositoryQuery extends RepositoryQuery {
}
interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
Optional<T> findById(ID id);