DATACMNS-102 - Allow Repositories to be composed of an arbitrary number of implementation classes.

We now use RepositoryComposition as backing implementation for repository method calls to implementations. We now scan for repository fragments during the repository configuration phase. Fragment implementation candidates derive from the repository interface declaration, specifically the declared interfaces and their order. We scan the class path during fragment scan for each interface and add discovered fragments to the repository composition. The name of the implementation is derived from the simple name of the interface and the implementation suffix. Qualified fragments are top-level interface declarations that are not annotated with NoRepositoryBean. Inherited interfaces are not considered as fragment candidates.

We create a RepositoryComposition from the discovered fragments in the order of interface declaration in the repository interface and supply the composition to the actual repository creation.

Original pull request: #222.
This commit is contained in:
Mark Paluch
2017-05-30 12:13:31 +02:00
committed by Oliver Gierke
parent 5e123ad7df
commit 92e8907030
24 changed files with 1974 additions and 560 deletions

View File

@@ -38,6 +38,7 @@ import org.springframework.data.util.Streamable;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
public class AnnotationRepositoryConfigurationSourceUnitTests {
@@ -62,14 +63,13 @@ public class AnnotationRepositoryConfigurationSourceUnitTests {
.contains(AnnotationRepositoryConfigurationSourceUnitTests.class.getPackage().getName());
}
@Test // DATACMNS-47
@Test // DATACMNS-47, DATACMNS-102
public void evaluatesExcludeFiltersCorrectly() {
Streamable<BeanDefinition> candidates = source.getCandidates(new DefaultResourceLoader());
assertThat(candidates).hasSize(1);
BeanDefinition candidate = candidates.iterator().next();
assertThat(candidate.getBeanClassName()).isEqualTo(MyRepository.class.getName());
assertThat(candidates).hasSize(2).extracting("beanClassName").containsOnly(MyRepository.class.getName(),
ComposedRepository.class.getName());
}
@Test // DATACMNS-47

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2017 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.config;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person;
/**
* @author Mark Paluch
*/
public interface ComposedRepository extends Repository<Person, Long>, Mixin {}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2017 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.config;
/**
* @author Mark Paluch
*/
public interface Mixin {
String getOne();
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright 2017 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.config;
/**
* @author Mark Paluch
*/
public class MixinImpl implements Mixin {
@Override
public String getOne() {
return "one";
}
}

View File

@@ -29,9 +29,10 @@ import org.springframework.data.repository.config.RepositoryBeanDefinitionRegist
/**
* Integration tests for {@link RepositoryBeanDefinitionRegistrarSupport}.
*
*
* @author Oliver Gierke
* @author Peter Rietzler
* @author Mark Paluch
*/
public class RepositoryBeanDefinitionRegistrarSupportIntegrationTests {
@@ -85,4 +86,9 @@ public class RepositoryBeanDefinitionRegistrarSupportIntegrationTests {
public void registersExtensionAsBeanDefinition() {
assertThat(context.getBean(DummyConfigurationExtension.class)).isNotNull();
}
@Test // DATACMNS-102
public void composedRepositoriesShouldBeAssembledCorrectly() {
assertThat(context.getBean(ComposedRepository.class).getOne()).isEqualTo("one");
}
}

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.data.repository.config;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import java.lang.annotation.Annotation;
@@ -37,6 +38,7 @@ import org.springframework.data.repository.core.support.DummyRepositoryFactoryBe
* Integration test for {@link RepositoryBeanDefinitionRegistrarSupport}.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class RepositoryBeanDefinitionRegistrarSupportUnitTests {
@@ -63,6 +65,9 @@ public class RepositoryBeanDefinitionRegistrarSupportUnitTests {
registrar.registerBeanDefinitions(metadata, registry);
assertBeanDefinitionRegisteredFor("myRepository");
assertBeanDefinitionRegisteredFor("composedRepository");
assertBeanDefinitionRegisteredFor("mixinImpl");
assertBeanDefinitionRegisteredFor("mixinImplFragment");
assertNoBeanDefinitionRegisteredFor("profileRepository");
}
@@ -100,7 +105,7 @@ public class RepositoryBeanDefinitionRegistrarSupportUnitTests {
setResourceLoader(new DefaultResourceLoader());
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/
@@ -109,7 +114,7 @@ public class RepositoryBeanDefinitionRegistrarSupportUnitTests {
return EnableRepositories.class;
}
/*
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/

View File

@@ -38,6 +38,7 @@ import org.springframework.data.repository.core.RepositoryMetadata;
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
*/
public class DefaultCrudMethodsUnitTests {
@@ -143,7 +144,7 @@ public class DefaultCrudMethodsUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
Optional.empty());
RepositoryComposition.empty());
return new DefaultCrudMethods(information);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2017 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.
@@ -64,7 +64,8 @@ public class DefaultRepositoryInformationUnitTests {
Method method = FooRepository.class.getMethod("findById", Integer.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY, Optional.empty());
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY,
RepositoryComposition.empty().withMethodLookup(MethodLookups.forRepositoryTypes(metadata)));
Method reference = information.getTargetClassMethod(method);
assertThat(reference.getDeclaringClass()).isEqualTo(REPOSITORY);
@@ -78,7 +79,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty().withMethodLookup(MethodLookups.forRepositoryTypes(metadata)));
assertThat(information.getTargetClassMethod(method)).isEqualTo(method);
}
@@ -88,7 +89,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation));
Method source = FooRepositoryCustom.class.getMethod("save", User.class);
Method expected = customImplementation.getClass().getMethod("save", User.class);
@@ -101,7 +102,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
assertThat(information.hasCustomMethod()).isFalse();
}
@@ -111,7 +112,7 @@ public class DefaultRepositoryInformationUnitTests {
DefaultRepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata,
PagingAndSortingRepository.class, Optional.empty());
PagingAndSortingRepository.class, RepositoryComposition.empty());
Method method = CustomRepository.class.getMethod("findAll", Pageable.class);
assertThat(information.isBaseClassMethod(method)).isTrue();
@@ -127,7 +128,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
Optional.empty());
RepositoryComposition.empty());
assertThat(information.getQueryMethods()).isEmpty();
}
@@ -137,7 +138,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
Method saveMethod = BaseRepository.class.getMethod("save", Object.class);
Method deleteMethod = BaseRepository.class.getMethod("delete", Object.class);
@@ -152,7 +153,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
Method intermediateMethod = BaseRepository.class.getMethod("genericMethodToOverride", String.class);
Method concreteMethod = ConcreteRepository.class.getMethod("genericMethodToOverride", String.class);
@@ -168,7 +169,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
Method queryMethod = getMethodFrom(ConcreteRepository.class, "findBySomethingDifferent");
@@ -181,7 +182,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
Method method = BaseRepository.class.getMethod("findById", Object.class);
@@ -193,7 +194,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(BossRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
Method method = BossRepository.class.getMethod("saveAll", Iterable.class);
Method reference = CrudRepository.class.getMethod("saveAll", Iterable.class);
@@ -206,7 +207,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomDefaultRepositoryMethodsRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.empty());
RepositoryComposition.empty());
assertThat(information.getQueryMethods()).allMatch(method -> !method.isBridge());
}
@@ -216,7 +217,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation));
Method source = FooRepositoryCustom.class.getMethod("exists", Object.class);
Method expected = customImplementation.getClass().getMethod("exists", Object.class);
@@ -230,7 +231,7 @@ public class DefaultRepositoryInformationUnitTests {
GenericsSaveRepositoryImpl customImplementation = new GenericsSaveRepositoryImpl();
RepositoryMetadata metadata = new DefaultRepositoryMetadata(GenericsSaveRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation).withMethodLookup(MethodLookups.forRepositoryTypes(metadata)));
Method customBaseRepositoryMethod = GenericsSaveRepository.class.getMethod("save", Object.class);
assertThat(information.isCustomMethod(customBaseRepositoryMethod)).isTrue();
@@ -241,7 +242,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation));
Method method = FooRepository.class.getMethod("staticMethod");
@@ -253,7 +254,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation));
Method method = FooRepository.class.getMethod("defaultMethod");
@@ -265,7 +266,7 @@ public class DefaultRepositoryInformationUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(DummyRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, DummyRepositoryImpl.class,
Optional.empty());
RepositoryComposition.empty());
Method method = DummyRepository.class.getMethod("saveAll", Iterable.class);
@@ -279,7 +280,7 @@ public class DefaultRepositoryInformationUnitTests {
SimpleSaveRepositoryImpl customImplementation = new SimpleSaveRepositoryImpl();
RepositoryMetadata metadata = new DefaultRepositoryMetadata(SimpleSaveRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, RepositoryFactorySupport.class,
Optional.of(customImplementation.getClass()));
RepositoryComposition.just(customImplementation).withMethodLookup(MethodLookups.forRepositoryTypes(metadata)));
Method customBaseRepositoryMethod = SimpleSaveRepository.class.getMethod("save", Object.class);
assertThat(information.isCustomMethod(customBaseRepositoryMethod)).isTrue();

View File

@@ -24,14 +24,13 @@ import reactor.core.publisher.Flux;
import rx.Observable;
import java.lang.reflect.Method;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import org.reactivestreams.Publisher;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
import org.springframework.data.repository.reactive.RxJava2CrudRepository;
@@ -46,7 +45,7 @@ import org.springframework.data.repository.reactive.RxJava2CrudRepository;
@RunWith(MockitoJUnitRunner.class)
public class ReactiveRepositoryInformationUnitTests {
static final Class<ReactiveJavaInterfaceWithGenerics> REPOSITORY = ReactiveJavaInterfaceWithGenerics.class;
static final Class<ReactiveJavaInterfaceWithGenerics> BASE_CLASS = ReactiveJavaInterfaceWithGenerics.class;
@Test // DATACMNS-836
public void discoversRxJava1MethodWithoutComparingReturnType() throws Exception {
@@ -129,9 +128,12 @@ public class ReactiveRepositoryInformationUnitTests {
private Method extractTargetMethodFromRepository(Class<?> repositoryType, String methodName, Class<?>... args)
throws NoSuchMethodException {
RepositoryInformation information = new ReactiveRepositoryInformation(new DefaultRepositoryMetadata(repositoryType),
REPOSITORY, Optional.empty());
return information.getTargetClassMethod(repositoryType.getMethod(methodName, args));
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryType);
RepositoryComposition composition = RepositoryComposition.of(RepositoryFragment.structural(BASE_CLASS))
.withMethodLookup(MethodLookups.forReactiveTypes(metadata));
return composition.findMethod(repositoryType.getMethod(methodName, args)).get();
}
interface RxJava1InterfaceWithGenerics extends Repository<User, String> {

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.repository.core.support;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import io.reactivex.Completable;

View File

@@ -0,0 +1,222 @@
/*
* Copyright 2017 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.core.support;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import lombok.Data;
import java.lang.reflect.Method;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.annotation.Id;
import org.springframework.data.domain.Example;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.util.ReflectionUtils;
/**
* Unit tests for {@link RepositoryComposition}.
*
* @author Mark Paluch
*/
@RunWith(MockitoJUnitRunner.class)
public class RepositoryCompositionUnitTests {
@Mock QueryByExampleExecutor<Person> queryByExampleExecutor;
@Mock PersonRepository backingRepo;
RepositoryComposition repositoryComposition;
@Before
public void before() {
RepositoryInformation repositoryInformation = new DefaultRepositoryInformation(
new DefaultRepositoryMetadata(PersonRepository.class), backingRepo.getClass(), RepositoryComposition.empty());
RepositoryFragment<QueryByExampleExecutor> mixin = RepositoryFragment.implemented(QueryByExampleExecutor.class,
queryByExampleExecutor);
RepositoryFragment<PersonRepository> base = RepositoryFragment.implemented(backingRepo);
repositoryComposition = RepositoryComposition.of(RepositoryFragments.of(mixin, base))
.withMethodLookup(MethodLookups.forRepositoryTypes(repositoryInformation));
}
@Test // DATACMNS-102
public void shouldReportIfEmpty() {
assertThat(RepositoryComposition.empty().isEmpty()).isTrue();
assertThat(repositoryComposition.isEmpty()).isFalse();
}
@Test // DATACMNS-102
public void shouldCallSaveOnBackingRepo() throws Throwable {
Method save = ReflectionUtils.findMethod(PersonRepository.class, "save", Person.class);
Method method = repositoryComposition.findMethod(save).get();
Person person = new Person();
repositoryComposition.invoke(method, person);
verify(backingRepo).save(person);
}
@Test // DATACMNS-102
public void shouldCallObjectSaveOnBackingRepo() throws Throwable {
Method save = ReflectionUtils.findMethod(PersonRepository.class, "save", Object.class);
Method method = repositoryComposition.findMethod(save).get();
Person person = new Person();
repositoryComposition.invoke(method, person);
verify(backingRepo).save((Object) person);
}
@Test // DATACMNS-102
public void shouldCallFindOneOnMixin() throws Throwable {
Method findOne = ReflectionUtils.findMethod(PersonRepository.class, "findOne", Example.class);
Method method = repositoryComposition.findMethod(findOne).get();
Person person = new Person();
Example<Person> example = Example.of(person);
repositoryComposition.invoke(method, example);
verify(queryByExampleExecutor).findOne(example);
}
@Test // DATACMNS-102
public void shouldCallMethodsInOrder() throws Throwable {
RepositoryInformation repositoryInformation = new DefaultRepositoryInformation(
new DefaultRepositoryMetadata(OrderedRepository.class), OrderedRepository.class, RepositoryComposition.empty());
RepositoryFragment<?> foo = RepositoryFragment.implemented(FooMixinImpl.INSTANCE);
RepositoryFragment<?> bar = RepositoryFragment.implemented(BarMixinImpl.INSTANCE);
RepositoryComposition fooBar = RepositoryComposition.of(RepositoryFragments.of(foo, bar))
.withMethodLookup(MethodLookups.forRepositoryTypes(repositoryInformation));
RepositoryComposition barFoo = RepositoryComposition.of(RepositoryFragments.of(bar, foo))
.withMethodLookup(MethodLookups.forRepositoryTypes(repositoryInformation));
Method getString = ReflectionUtils.findMethod(OrderedRepository.class, "getString");
assertThat(fooBar.invoke(fooBar.findMethod(getString).get())).isEqualTo("foo");
assertThat(barFoo.invoke(barFoo.findMethod(getString).get())).isEqualTo("bar");
}
@Test // DATACMNS-102
public void shouldValidateStructuralFragments() {
RepositoryComposition mixed = RepositoryComposition.of(RepositoryFragment.structural(QueryByExampleExecutor.class),
RepositoryFragment.implemented(backingRepo));
assertThatExceptionOfType(IllegalStateException.class) //
.isThrownBy(mixed::validateImplementation) //
.withMessageContaining(
"Fragment org.springframework.data.repository.query.QueryByExampleExecutor has no implementation.");
}
@Test // DATACMNS-102
public void shouldValidateImplementationFragments() {
RepositoryComposition mixed = RepositoryComposition.of(RepositoryFragment.implemented(backingRepo));
mixed.validateImplementation();
}
@Test // DATACMNS-102
@SuppressWarnings("rawtypes")
public void shouldAppendCorrectly() {
RepositoryFragment<PersonRepository> initial = RepositoryFragment.implemented(backingRepo);
RepositoryFragment<QueryByExampleExecutor> structural = RepositoryFragment.structural(QueryByExampleExecutor.class);
assertThat(RepositoryComposition.of(initial).append(structural).getFragments()).containsSequence(initial,
structural);
assertThat(RepositoryComposition.of(initial).append(RepositoryFragments.of(structural)).getFragments())
.containsSequence(initial, structural);
}
interface PersonRepository extends Repository<Person, String>, QueryByExampleExecutor<Person> {
Person save(Person entity);
Person save(Object entity);
Person findOne(Person entity);
}
@Data
static class Person {
@Id String id;
}
@Data
static class Contact {
@Id String id;
}
interface OrderedRepository extends Repository<Person, String>, FooMixin, BarMixin {
}
interface FooMixin {
String getString();
}
enum FooMixinImpl implements FooMixin {
INSTANCE;
@Override
public String getString() {
return "foo";
}
}
interface BarMixin {
String getString();
}
enum BarMixinImpl implements BarMixin {
INSTANCE;
@Override
public String getString() {
return "bar";
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2016 the original author or authors.
* Copyright 2011-2017 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.
@@ -51,6 +51,7 @@ 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.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.sample.User;
import org.springframework.data.util.Version;
@@ -125,6 +126,17 @@ public class RepositoryFactorySupportUnitTests {
verify(backingRepo, times(0)).findById(1);
}
@Test // DATACMNS-102
public void invokesCustomMethodCompositionMethodIfItRedeclaresACRUDOne() {
ObjectRepository repository = factory.getRepository(ObjectRepository.class,
RepositoryFragments.just(customImplementation));
repository.findById(1);
verify(customImplementation, times(1)).findById(1);
verify(backingRepo, times(0)).findById(1);
}
@Test
public void createsRepositoryInstanceWithCustomIntermediateRepository() {