Skip to content

Commit 79b15df

Browse files
committed
DATACMNS-763 - Fixed constructor in example of custom repository base class.
Added hint to which constructor of the superclass to override. Added test cas e to make sure the expected types are advertised in the case of an error.
1 parent 9220770 commit 79b15df

File tree

3 files changed

+48
-5
lines changed

3 files changed

+48
-5
lines changed

src/main/asciidoc/repositories.adoc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,15 @@ Standard CRUD functionality repositories usually have queries on the underlying
107107

108108
. Declare an interface extending Repository or one of its subinterfaces and type it to the domain class and ID type that it will handle.
109109
+
110+
110111
[source, java]
111112
----
112113
interface PersonRepository extends Repository<Person, Long> { … }
113114
----
114115

115116
. Declare query methods on the interface.
116117
+
118+
117119
[source, java]
118120
----
119121
interface PersonRepository extends Repository<Person, Long> {
@@ -123,16 +125,19 @@ interface PersonRepository extends Repository<Person, Long> {
123125

124126
. Set up Spring to create proxy instances for those interfaces. Either via <<repositories.create-instances.java-config,JavaConfig>>:
125127
+
128+
126129
[source, java]
127130
----
128131
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
129132
130133
@EnableJpaRepositories
131134
class Config {}
132135
----
136+
133137
+
134138
or via <<repositories.create-instances,XML configuration>>:
135139
+
140+
136141
[source, xml]
137142
----
138143
<?xml version="1.0" encoding="UTF-8"?>
@@ -148,13 +153,15 @@ or via <<repositories.create-instances,XML configuration>>:
148153
149154
</beans>
150155
----
156+
151157
+
152158
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`.
153-
159+
+
154160
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.
155161

156162
. Get the repository instance injected and use it.
157163
+
164+
158165
[source, java]
159166
----
160167
public class SomeClient {
@@ -578,8 +585,9 @@ public class MyRepositoryImpl<T, ID extends Serializable>
578585
579586
private final EntityManager entityManager;
580587
581-
public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
582-
super(domainClass, entityManager);
588+
public MyRepositoryImpl(JpaEntityInformation entityInformation,
589+
EntityManager entityManager) {
590+
super(entityInformation, entityManager);
583591
584592
// Keep the EntityManager around to used from the newly introduced methods.
585593
this.entityManager = entityManager;
@@ -592,6 +600,8 @@ public class MyRepositoryImpl<T, ID extends Serializable>
592600
----
593601
====
594602

603+
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).
604+
595605
The default behavior of the Spring `<repositories />` 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`.
596606

597607
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:

src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,16 @@ protected final <R> R getTargetRepositoryViaReflection(RepositoryInformation inf
335335
Constructor<?> constructor = ReflectionUtils.findConstructor(baseClass, constructorArguments);
336336

337337
if (constructor == null) {
338+
339+
List<Class<?>> argumentTypes = new ArrayList<Class<?>>(constructorArguments.length);
340+
341+
for (Object argument : constructorArguments) {
342+
argumentTypes.add(argument.getClass());
343+
}
344+
338345
throw new IllegalStateException(String.format(
339-
"No suitable constructor found on %s to match the given arguments: %s", baseClass, constructorArguments));
346+
"No suitable constructor found on %s to match the given arguments: %s. Make sure you implement a constructor taking these",
347+
baseClass, argumentTypes));
340348
}
341349

342350
return (R) BeanUtils.instantiateClass(constructor, constructorArguments);

src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
import org.springframework.data.repository.PagingAndSortingRepository;
5050
import org.springframework.data.repository.Repository;
5151
import org.springframework.data.repository.RepositoryDefinition;
52+
import org.springframework.data.repository.core.EntityInformation;
5253
import org.springframework.data.repository.core.NamedQueries;
54+
import org.springframework.data.repository.core.RepositoryInformation;
5355
import org.springframework.data.repository.core.RepositoryMetadata;
5456
import org.springframework.data.repository.query.RepositoryQuery;
5557
import org.springframework.data.repository.sample.User;
@@ -291,7 +293,7 @@ public void wrapsExecutionResultIntoCompletableFutureWithEntityCollectionIfConfi
291293
}
292294

293295
/**
294-
* @see @see DATACMNS-714
296+
* @see DATACMNS-714
295297
*/
296298
@Test
297299
public void wrapsExecutionResultIntoListenableFutureWithEntityCollectionIfConfigured() throws Exception {
@@ -303,6 +305,24 @@ public void wrapsExecutionResultIntoListenableFutureWithEntityCollectionIfConfig
303305
expect(prepareConvertingRepository(reference).readAllByLastname("Foo"), reference);
304306
}
305307

308+
/**
309+
* @see DATACMNS-763
310+
*/
311+
@Test
312+
@SuppressWarnings("rawtypes")
313+
public void rejectsRepositoryBaseClassWithInvalidConstructor() {
314+
315+
RepositoryInformation information = mock(RepositoryInformation.class);
316+
doReturn(CustomRepositoryBaseClass.class).when(information).getRepositoryBaseClass();
317+
EntityInformation entityInformation = mock(EntityInformation.class);
318+
319+
exception.expect(IllegalStateException.class);
320+
exception.expectMessage(entityInformation.getClass().getName());
321+
exception.expectMessage(String.class.getName());
322+
323+
factory.getTargetRepositoryViaReflection(information, entityInformation, "Foo");
324+
}
325+
306326
private ConvertingRepository prepareConvertingRepository(final Object expectedValue) {
307327

308328
when(factory.queryOne.execute(Mockito.any(Object[].class))).then(new Answer<Object>() {
@@ -411,4 +431,9 @@ interface ConvertingRepository extends Repository<Object, Long> {
411431
@Async
412432
ListenableFuture<List<User>> readAllByLastname(String lastname);
413433
}
434+
435+
static class CustomRepositoryBaseClass {
436+
437+
public CustomRepositoryBaseClass(EntityInformation<?, ?> information) {}
438+
}
414439
}

0 commit comments

Comments
 (0)