Skip to content

Commit de7a7ca

Browse files
committed
DATACMNS-1438 - Repositories now favors primary repositories.
If multiple repositories pointing to a single domain type are found we now favor the one registered as primary bean definition. This allows allows reliable disambiguation on which repository is supposed to be used for generic repository interactions. Related tickets: DATAREST-923.
1 parent 97390a1 commit de7a7ca

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

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

+33-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.beans.factory.BeanFactory;
2828
import org.springframework.beans.factory.BeanFactoryUtils;
2929
import org.springframework.beans.factory.ListableBeanFactory;
30+
import org.springframework.beans.factory.config.BeanDefinition;
31+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3032
import org.springframework.data.mapping.PersistentEntity;
3133
import org.springframework.data.mapping.context.MappingContext;
3234
import org.springframework.data.repository.core.EntityInformation;
@@ -91,7 +93,7 @@ private void populateRepositoryFactoryInformation(ListableBeanFactory factory) {
9193
}
9294
}
9395

94-
@SuppressWarnings({ "rawtypes", "unchecked" })
96+
@SuppressWarnings("rawtypes")
9597
private synchronized void cacheRepositoryFactory(String name) {
9698

9799
RepositoryFactoryInformation repositoryFactoryInformation = beanFactory.get().getBean(name,
@@ -101,15 +103,13 @@ private synchronized void cacheRepositoryFactory(String name) {
101103

102104
RepositoryInformation information = repositoryFactoryInformation.getRepositoryInformation();
103105
Set<Class<?>> alternativeDomainTypes = information.getAlternativeDomainTypes();
104-
String beanName = BeanFactoryUtils.transformedBeanName(name);
105106

106107
Set<Class<?>> typesToRegister = new HashSet<>(alternativeDomainTypes.size() + 1);
107108
typesToRegister.add(domainType);
108109
typesToRegister.addAll(alternativeDomainTypes);
109110

110111
for (Class<?> type : typesToRegister) {
111-
this.repositoryFactoryInfos.put(type, repositoryFactoryInformation);
112-
this.repositoryBeanNames.put(type, beanName);
112+
cacheFirstOrPrimary(type, repositoryFactoryInformation, BeanFactoryUtils.transformedBeanName(name));
113113
}
114114
}
115115

@@ -263,6 +263,35 @@ public Iterator<Class<?>> iterator() {
263263
return repositoryFactoryInfos.keySet().iterator();
264264
}
265265

266+
/**
267+
* Caches the repository information for the given domain type or overrides existing information in case the bean name
268+
* points to a primary bean definition.
269+
*
270+
* @param type must not be {@literal null}.
271+
* @param information must not be {@literal null}.
272+
* @param name must not be {@literal null}.
273+
*/
274+
@SuppressWarnings({ "rawtypes", "unchecked" })
275+
private void cacheFirstOrPrimary(Class<?> type, RepositoryFactoryInformation information, String name) {
276+
277+
if (repositoryBeanNames.containsKey(type)) {
278+
279+
Boolean presentAndPrimary = beanFactory //
280+
.filter(ConfigurableListableBeanFactory.class::isInstance) //
281+
.map(ConfigurableListableBeanFactory.class::cast) //
282+
.map(it -> it.getBeanDefinition(name)) //
283+
.map(BeanDefinition::isPrimary) //
284+
.orElse(false);
285+
286+
if (!presentAndPrimary) {
287+
return;
288+
}
289+
}
290+
291+
this.repositoryFactoryInfos.put(type, information);
292+
this.repositoryBeanNames.put(type, name);
293+
}
294+
266295
/**
267296
* Null-object to avoid nasty {@literal null} checks in cache lookups.
268297
*

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

+32
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3434
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
3535
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
36+
import org.springframework.context.annotation.Primary;
3637
import org.springframework.context.support.GenericApplicationContext;
3738
import org.springframework.data.mapping.PersistentEntity;
3839
import org.springframework.data.mapping.context.SampleMappingContext;
@@ -158,6 +159,28 @@ public void exposesRepositoryForProxyType() {
158159
assertThat(repositories.getRepositoryFor(proxy.getClass())).isNotEmpty();
159160
}
160161

162+
@Test // DATACMNS-1448
163+
public void keepsPrimaryRepositoryInCaseOfMultipleOnes() {
164+
165+
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
166+
beanFactory.registerBeanDefinition("first", getRepositoryBeanDefinition(FirstRepository.class));
167+
168+
AbstractBeanDefinition definition = getRepositoryBeanDefinition(PrimaryRepository.class);
169+
definition.setPrimary(true);
170+
171+
beanFactory.registerBeanDefinition("primary", definition);
172+
beanFactory.registerBeanDefinition("third", getRepositoryBeanDefinition(ThirdRepository.class));
173+
174+
context = new GenericApplicationContext(beanFactory);
175+
context.refresh();
176+
177+
Repositories repositories = new Repositories(beanFactory);
178+
179+
assertThat(repositories.getRepositoryFor(SomeEntity.class)).hasValueSatisfying(it -> {
180+
assertThat(it).isInstanceOf(PrimaryRepository.class);
181+
});
182+
}
183+
161184
class Person {}
162185

163186
class Address {}
@@ -244,4 +267,13 @@ interface Sample {}
244267
static class SampleEntity implements Sample {}
245268

246269
interface SampleRepository extends Repository<Sample, Long> {}
270+
271+
interface SomeEntity {}
272+
273+
interface FirstRepository extends Repository<SomeEntity, Long> {}
274+
275+
@Primary
276+
interface PrimaryRepository extends Repository<SomeEntity, Long> {}
277+
278+
interface ThirdRepository extends Repository<SomeEntity, Long> {}
247279
}

0 commit comments

Comments
 (0)