diff --git a/src/main/asciidoc/repositories.adoc b/src/main/asciidoc/repositories.adoc index f4c9977ea..5768e9123 100644 --- a/src/main/asciidoc/repositories.adoc +++ b/src/main/asciidoc/repositories.adoc @@ -107,6 +107,7 @@ Standard CRUD functionality repositories usually have queries on the underlying . Declare an interface extending Repository or one of its subinterfaces and type it to the domain class and ID type that it will handle. + + [source, java] ---- interface PersonRepository extends Repository { … } @@ -114,6 +115,7 @@ interface PersonRepository extends Repository { … } . Declare query methods on the interface. + + [source, java] ---- interface PersonRepository extends Repository { @@ -123,6 +125,7 @@ interface PersonRepository extends Repository { . Set up Spring to create proxy instances for those interfaces. Either via <>: + + [source, java] ---- import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @@ -130,9 +133,11 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {} ---- + + or via <>: + + [source, xml] ---- @@ -148,13 +153,15 @@ or via <>: ---- + + The JPA namespace is used in this example. If you are using the repository abstraction for any other store, you need to change this to the appropriate namespace declaration of your store module which should be exchanging `jpa` in favor of, for example, `mongodb`. - ++ Also, note that the JavaConfig variant doesn't configure a package explictly as the package of the annotated class is used by default. To customize the package to scan use one of the `basePackage…` attribute of the data-store specific repository `@Enable…`-annotation. . Get the repository instance injected and use it. + + [source, java] ---- public class SomeClient { @@ -578,8 +585,9 @@ public class MyRepositoryImpl private final EntityManager entityManager; - public MyRepositoryImpl(Class domainClass, EntityManager entityManager) { - super(domainClass, entityManager); + public MyRepositoryImpl(JpaEntityInformation entityInformation, + EntityManager entityManager) { + super(entityInformation, entityManager); // Keep the EntityManager around to used from the newly introduced methods. this.entityManager = entityManager; @@ -592,6 +600,8 @@ public class MyRepositoryImpl ---- ==== +WARNING: The class needs to have a constructor of the super class which the store-specific repository factory implementation is using. In case the repository base class has multiple constructors, override the one taking an `EntityInformation` plus a store specific infrastructure object (e.g. an `EntityManager` or a template class). + The default behavior of the Spring `` namespace is to provide an implementation for all interfaces that fall under the `base-package`. This means that if left in its current state, an implementation instance of `MyRepository` will be created by Spring. This is of course not desired as it is just supposed to act as an intermediary between `Repository` and the actual repository interfaces you want to define for each entity. To exclude an interface that extends `Repository` from being instantiated as a repository instance, you can either annotate it with `@NoRepositoryBean` (as seen above) or move it outside of the configured `base-package`. The final step is to make the Spring Data infrastructure aware of the customized repository base class. In JavaConfig this is achieved by using the `repositoryBaseClass` attribute of the `@Enable…Repositories` annotation: diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 98be452ca..56c5ae52b 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -335,8 +335,16 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware { Constructor constructor = ReflectionUtils.findConstructor(baseClass, constructorArguments); if (constructor == null) { + + List> argumentTypes = new ArrayList>(constructorArguments.length); + + for (Object argument : constructorArguments) { + argumentTypes.add(argument.getClass()); + } + throw new IllegalStateException(String.format( - "No suitable constructor found on %s to match the given arguments: %s", baseClass, constructorArguments)); + "No suitable constructor found on %s to match the given arguments: %s. Make sure you implement a constructor taking these", + baseClass, argumentTypes)); } return (R) BeanUtils.instantiateClass(constructor, constructorArguments); diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 583f127a4..7024ecde5 100644 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -49,7 +49,9 @@ import org.springframework.data.domain.Sort; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.RepositoryDefinition; +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.RepositoryQuery; import org.springframework.data.repository.sample.User; @@ -291,7 +293,7 @@ public class RepositoryFactorySupportUnitTests { } /** - * @see @see DATACMNS-714 + * @see DATACMNS-714 */ @Test public void wrapsExecutionResultIntoListenableFutureWithEntityCollectionIfConfigured() throws Exception { @@ -303,6 +305,24 @@ public class RepositoryFactorySupportUnitTests { expect(prepareConvertingRepository(reference).readAllByLastname("Foo"), reference); } + /** + * @see DATACMNS-763 + */ + @Test + @SuppressWarnings("rawtypes") + public void rejectsRepositoryBaseClassWithInvalidConstructor() { + + RepositoryInformation information = mock(RepositoryInformation.class); + doReturn(CustomRepositoryBaseClass.class).when(information).getRepositoryBaseClass(); + EntityInformation entityInformation = mock(EntityInformation.class); + + exception.expect(IllegalStateException.class); + exception.expectMessage(entityInformation.getClass().getName()); + exception.expectMessage(String.class.getName()); + + factory.getTargetRepositoryViaReflection(information, entityInformation, "Foo"); + } + private ConvertingRepository prepareConvertingRepository(final Object expectedValue) { when(factory.queryOne.execute(Mockito.any(Object[].class))).then(new Answer() { @@ -411,4 +431,9 @@ public class RepositoryFactorySupportUnitTests { @Async ListenableFuture> readAllByLastname(String lastname); } + + static class CustomRepositoryBaseClass { + + public CustomRepositoryBaseClass(EntityInformation information) {} + } }