Skip to content

Commit 0d92abc

Browse files
odrotbohmmp911de
authored andcommitted
Repositories now allows lookup of parent repositories for sub-types.
When inheritance is used for aggregates, lookups of the repository for a Child aggregate instance have so far failed to return a repository registered for the Parent. Client code had to consider that scenario explicitly. This commit introduces an additional lookup step in case we cannot find a repository for an aggregate type immediately. In this case, we then check for assignability of any of the known aggregate types we have registered repositories for and the type requested. I.e. for a request for the repository of Child, a repository, explicitly registered to manage Child instances would still be used. In the sole presence of a repository managing Parent instances, that would be returned for the request for Child, too. Original pull request: #2406.
1 parent a01c459 commit 0d92abc

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

src/main/java/org/springframework/data/repository/support/Repositories.java

+33-3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.data.util.ProxyUtils;
4040
import org.springframework.util.Assert;
4141
import org.springframework.util.ClassUtils;
42+
import org.springframework.util.ConcurrentLruCache;
4243

4344
/**
4445
* Wrapper class to access repository instances obtained from a {@link ListableBeanFactory}.
@@ -58,6 +59,8 @@ public class Repositories implements Iterable<Class<?>> {
5859
private final Optional<BeanFactory> beanFactory;
5960
private final Map<Class<?>, String> repositoryBeanNames;
6061
private final Map<Class<?>, RepositoryFactoryInformation<Object, Object>> repositoryFactoryInfos;
62+
private final ConcurrentLruCache<Class<?>, Class<?>> domainTypeMapping = new ConcurrentLruCache<>(64,
63+
this::getRepositoryDomainTypeFor);
6164

6265
/**
6366
* Constructor to create the {@link #NONE} instance.
@@ -124,7 +127,7 @@ public boolean hasRepositoryFor(Class<?> domainClass) {
124127

125128
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
126129

127-
Class<?> userClass = ProxyUtils.getUserClass(domainClass);
130+
Class<?> userClass = domainTypeMapping.get(ProxyUtils.getUserClass(domainClass));
128131

129132
return repositoryFactoryInfos.containsKey(userClass);
130133
}
@@ -139,7 +142,7 @@ public Optional<Object> getRepositoryFor(Class<?> domainClass) {
139142

140143
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
141144

142-
Class<?> userClass = ProxyUtils.getUserClass(domainClass);
145+
Class<?> userClass = domainTypeMapping.get(ProxyUtils.getUserClass(domainClass));
143146
Optional<String> repositoryBeanName = Optional.ofNullable(repositoryBeanNames.get(userClass));
144147

145148
return beanFactory.flatMap(it -> repositoryBeanName.map(it::getBean));
@@ -157,7 +160,7 @@ private RepositoryFactoryInformation<Object, Object> getRepositoryFactoryInfoFor
157160

158161
Assert.notNull(domainClass, DOMAIN_TYPE_MUST_NOT_BE_NULL);
159162

160-
Class<?> userType = ProxyUtils.getUserClass(domainClass);
163+
Class<?> userType = domainTypeMapping.get(ProxyUtils.getUserClass(domainClass));
161164
RepositoryFactoryInformation<Object, Object> repositoryInfo = repositoryFactoryInfos.get(userType);
162165

163166
if (repositoryInfo != null) {
@@ -303,6 +306,33 @@ private void cacheFirstOrPrimary(Class<?> type, RepositoryFactoryInformation inf
303306
this.repositoryBeanNames.put(type, name);
304307
}
305308

309+
/**
310+
* Returns the repository domain type for which to look up the repository. The input can either be a repository
311+
* managed type directly. Or it can be a sub-type of a repository managed one, in which case we check the domain types
312+
* we have repositories registered for for assignability.
313+
*
314+
* @param domainType must not be {@literal null}.
315+
* @return
316+
*/
317+
private Class<?> getRepositoryDomainTypeFor(Class<?> domainType) {
318+
319+
Assert.notNull(domainType, "Domain type must not be null!");
320+
321+
Set<Class<?>> declaredTypes = repositoryBeanNames.keySet();
322+
323+
if (declaredTypes.contains(domainType)) {
324+
return domainType;
325+
}
326+
327+
for (Class<?> declaredType : declaredTypes) {
328+
if (declaredType.isAssignableFrom(domainType)) {
329+
return declaredType;
330+
}
331+
}
332+
333+
return domainType;
334+
}
335+
306336
/**
307337
* Null-object to avoid nasty {@literal null} checks in cache lookups.
308338
*

src/test/java/org/springframework/data/repository/support/RepositoriesUnitTests.java

+51-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import org.mockito.junit.jupiter.MockitoExtension;
3030
import org.mockito.junit.jupiter.MockitoSettings;
3131
import org.mockito.quality.Strictness;
32-
3332
import org.springframework.aop.framework.ProxyFactory;
3433
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3534
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
@@ -206,6 +205,47 @@ void keepsPrimaryRepositoryInCaseOfMultipleOnesIfContextIsNotAConfigurableListab
206205
});
207206
}
208207

208+
@Test // GH-2406
209+
void exposesParentRepositoryForChildIfOnlyParentRepositoryIsRegistered() {
210+
211+
Repositories repositories = bootstrapRepositories(ParentRepository.class);
212+
213+
assertRepositoryAvailableFor(repositories, Child.class, ParentRepository.class);
214+
}
215+
216+
@Test // GH-2406
217+
void usesChildRepositoryIfRegistered() {
218+
219+
Repositories repositories = bootstrapRepositories(ParentRepository.class, ChildRepository.class);
220+
221+
assertRepositoryAvailableFor(repositories, Child.class, ChildRepository.class);
222+
}
223+
224+
private void assertRepositoryAvailableFor(Repositories repositories, Class<?> domainTypem,
225+
Class<?> repositoryInterface) {
226+
227+
assertThat(repositories.hasRepositoryFor(domainTypem)).isTrue();
228+
assertThat(repositories.getRepositoryFor(domainTypem))
229+
.hasValueSatisfying(it -> assertThat(it).isInstanceOf(repositoryInterface));
230+
assertThat(repositories.getRepositoryInformationFor(domainTypem))
231+
.hasValueSatisfying(it -> assertThat(it.getRepositoryInterface()).isEqualTo(repositoryInterface));
232+
}
233+
234+
private Repositories bootstrapRepositories(Class<?>... repositoryInterfaces) {
235+
236+
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
237+
238+
for (Class<?> repositoryInterface : repositoryInterfaces) {
239+
beanFactory.registerBeanDefinition(repositoryInterface.getName(),
240+
getRepositoryBeanDefinition(repositoryInterface));
241+
}
242+
243+
context = new GenericApplicationContext(beanFactory);
244+
context.refresh();
245+
246+
return new Repositories(context);
247+
}
248+
209249
class Person {}
210250

211251
class Address {}
@@ -301,4 +341,14 @@ interface FirstRepository extends Repository<SomeEntity, Long> {}
301341
interface PrimaryRepository extends Repository<SomeEntity, Long> {}
302342

303343
interface ThirdRepository extends Repository<SomeEntity, Long> {}
344+
345+
// GH-2406
346+
347+
static class Parent {}
348+
349+
static class Child extends Parent {}
350+
351+
interface ParentRepository extends Repository<Parent, Long> {}
352+
353+
interface ChildRepository extends Repository<Child, Long> {}
304354
}

0 commit comments

Comments
 (0)