Skip to content

Commit 9224b5a

Browse files
committed
DATAJDBC-293 - DI logic now runs after Spring Boot.
So far the lookup of `NamedParameterJdbcOperations` and `DataAccessStrategy` happend before Spring Boot injected its beans, resulting in failure to consider those beans. This logic is now in the `JdbcRepositoryFactoryBean` which does see the beans created by Spring Boot.
1 parent 857572d commit 9224b5a

File tree

6 files changed

+95
-242
lines changed

6 files changed

+95
-242
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java

Lines changed: 3 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -87,103 +87,17 @@ public void registerBeansForRoot(BeanDefinitionRegistry registry, RepositoryConf
8787
}
8888
}
8989

90-
/*
90+
/*
9191
* (non-Javadoc)
9292
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
9393
*/
9494
@Override
9595
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
9696

97-
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class, true);
98-
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class, false);
99-
}
100-
101-
private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source,
102-
String attributeName, String propertyName, Class<?> classRef, boolean required) {
103-
104-
Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText);
105-
106-
String beanName = beanNameRef.orElseGet(() -> determineMatchingBeanName(propertyName, classRef, required));
107-
108-
if (beanName != null) {
109-
builder.addPropertyReference(propertyName, beanName);
110-
} else {
111-
Assert.isTrue(!required,
112-
"The beanName must not be null when requested as 'required'. Please report this as a bug.");
113-
}
114-
115-
}
116-
117-
@Nullable
118-
private String determineMatchingBeanName(String propertyName, Class<?> classRef, boolean required) {
119-
120-
if (this.beanFactory == null) {
121-
return nullOrThrowException(required,
122-
() -> new NoSuchBeanDefinitionException(classRef, "No BeanFactory available."));
123-
}
124-
125-
List<String> beanNames = Arrays.asList(beanFactory.getBeanNamesForType(classRef));
126-
127-
if (beanNames.isEmpty()) {
128-
return nullOrThrowException(required,
129-
() -> new NoSuchBeanDefinitionException(classRef, String.format("No bean of type %s available", classRef)));
130-
}
131-
132-
if (beanNames.size() == 1) {
133-
return beanNames.get(0);
134-
}
135-
136-
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
137-
138-
return nullOrThrowException(required,
139-
() -> new NoSuchBeanDefinitionException(String.format(
140-
"BeanFactory does not implement ConfigurableListableBeanFactory when trying to find bean of type %s.",
141-
classRef)));
142-
}
143-
144-
List<String> primaryBeanNames = getPrimaryBeanDefinitions(beanNames, (ConfigurableListableBeanFactory) beanFactory);
145-
146-
if (primaryBeanNames.size() == 1) {
147-
return primaryBeanNames.get(0);
148-
}
149-
150-
if (primaryBeanNames.size() > 1) {
151-
throw new NoUniqueBeanDefinitionException(classRef, primaryBeanNames.size(),
152-
"more than one 'primary' bean found among candidates: " + primaryBeanNames);
153-
}
154-
155-
for (String beanName : beanNames) {
156-
157-
if (propertyName.equals(beanName)
158-
|| ObjectUtils.containsElement(beanFactory.getAliases(beanName), propertyName)) {
159-
return beanName;
160-
}
161-
}
162-
163-
return nullOrThrowException(required,
164-
() -> new NoSuchBeanDefinitionException(String.format("No bean of name %s found.", propertyName)));
97+
builder.addPropertyValue("jdbcOperationsRef", source.getAttribute("jdbcOperationsRef").orElse(null));
98+
builder.addPropertyValue("dataAccessStrategyRef", source.getAttribute("dataAccessStrategyRef").orElse(null));
16599
}
166100

167-
private static List<String> getPrimaryBeanDefinitions(List<String> beanNames,
168-
ConfigurableListableBeanFactory beanFactory) {
169-
170-
ArrayList<String> primaryBeanNames = new ArrayList<>();
171-
for (String name : beanNames) {
172101

173-
if (beanFactory.getBeanDefinition(name).isPrimary()) {
174-
primaryBeanNames.add(name);
175-
}
176-
}
177-
return primaryBeanNames;
178-
}
179-
180-
@Nullable
181-
private static String nullOrThrowException(boolean required, Supplier<RuntimeException> exception) {
182-
183-
if (required) {
184-
throw exception.get();
185-
}
186-
return null;
187-
}
188102

189103
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616
package org.springframework.data.jdbc.repository.support;
1717

1818
import java.io.Serializable;
19+
import java.util.Map;
1920

21+
import org.springframework.beans.factory.BeanFactory;
22+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
2023
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2125
import org.springframework.context.ApplicationEventPublisher;
2226
import org.springframework.context.ApplicationEventPublisherAware;
2327
import org.springframework.data.jdbc.core.DataAccessStrategy;
@@ -31,7 +35,9 @@
3135
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
3236
import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport;
3337
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
38+
import org.springframework.lang.Nullable;
3439
import org.springframework.util.Assert;
40+
import org.springframework.util.StringUtils;
3541

3642
/**
3743
* Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of
@@ -47,11 +53,14 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
4753
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> implements ApplicationEventPublisherAware {
4854

4955
private ApplicationEventPublisher publisher;
56+
private ConfigurableListableBeanFactory beanFactory;
5057
private RelationalMappingContext mappingContext;
5158
private RelationalConverter converter;
5259
private DataAccessStrategy dataAccessStrategy;
5360
private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
5461
private NamedParameterJdbcOperations operations;
62+
private String dataAccessStrategyRef;
63+
private String jdbcOperationsRef;
5564

5665
/**
5766
* Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface.
@@ -101,6 +110,10 @@ public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
101110
this.dataAccessStrategy = dataAccessStrategy;
102111
}
103112

113+
public void setDataAccessStrategyRef(String dataAccessStrategyRef) {
114+
this.dataAccessStrategyRef = dataAccessStrategyRef;
115+
}
116+
104117
/**
105118
* @param queryMappingConfiguration can be {@literal null}. {@link #afterPropertiesSet()} defaults to
106119
* {@link QueryMappingConfiguration#EMPTY} if {@literal null}.
@@ -126,11 +139,23 @@ public void setJdbcOperations(NamedParameterJdbcOperations operations) {
126139
this.operations = operations;
127140
}
128141

142+
public void setJdbcOperationsRef(String jdbcOperationsRef) {
143+
this.jdbcOperationsRef = jdbcOperationsRef;
144+
}
145+
129146
@Autowired
130147
public void setConverter(RelationalConverter converter) {
131148
this.converter = converter;
132149
}
133150

151+
@Override
152+
public void setBeanFactory(BeanFactory beanFactory) {
153+
154+
super.setBeanFactory(beanFactory);
155+
156+
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
157+
}
158+
134159
/*
135160
* (non-Javadoc)
136161
* @see org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport#afterPropertiesSet()
@@ -141,17 +166,70 @@ public void afterPropertiesSet() {
141166
Assert.state(this.mappingContext != null, "MappingContext is required and must not be null!");
142167
Assert.state(this.converter != null, "RelationalConverter is required and must not be null!");
143168

144-
if (dataAccessStrategy == null) {
145-
146-
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(mappingContext);
147-
this.dataAccessStrategy = new DefaultDataAccessStrategy(sqlGeneratorSource, mappingContext, converter,
148-
operations);
149-
}
169+
ensureJdbcOperationsIsInitialized();
170+
ensureDataAccessStrategyIsInitialized();
150171

151172
if (queryMappingConfiguration == null) {
152173
this.queryMappingConfiguration = QueryMappingConfiguration.EMPTY;
153174
}
154175

155176
super.afterPropertiesSet();
156177
}
178+
179+
private void ensureJdbcOperationsIsInitialized() {
180+
181+
if (operations != null) {
182+
return;
183+
}
184+
185+
operations = findBeanByNameOrType(NamedParameterJdbcOperations.class, jdbcOperationsRef);
186+
187+
}
188+
189+
private void ensureDataAccessStrategyIsInitialized() {
190+
191+
if (dataAccessStrategy != null) {
192+
return;
193+
}
194+
195+
dataAccessStrategy = findBeanByNameOrType(DataAccessStrategy.class, dataAccessStrategyRef);
196+
197+
if (dataAccessStrategy != null) {
198+
return;
199+
}
200+
201+
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(mappingContext);
202+
this.dataAccessStrategy = new DefaultDataAccessStrategy(sqlGeneratorSource, mappingContext, converter,
203+
operations);
204+
}
205+
206+
@Nullable
207+
private <B> B findBeanByNameOrType(Class<B> beanType, String beanName) {
208+
209+
if (beanFactory == null) {
210+
return null;
211+
}
212+
213+
if (!StringUtils.isEmpty(beanName)) {
214+
return beanFactory.getBean(beanName, beanType);
215+
}
216+
217+
Map<String, B> beansOfType = beanFactory.getBeansOfType(beanType);
218+
219+
if (beansOfType.size() == 0) {
220+
return null;
221+
}
222+
223+
if (beansOfType.size() == 1) {
224+
return beansOfType.values().iterator().next();
225+
}
226+
227+
for (Map.Entry<String, B> entry : beansOfType.entrySet()) {
228+
if (beanFactory.getBeanDefinition(entry.getKey()).isPrimary()) {
229+
return entry.getValue();
230+
}
231+
}
232+
233+
throw new NoUniqueBeanDefinitionException(beanType, beansOfType.keySet());
234+
}
157235
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisCustomizingNamespaceHsqlIntegrationTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.springframework.beans.factory.annotation.Autowired;
3333
import org.springframework.context.annotation.Bean;
3434
import org.springframework.context.annotation.Import;
35+
import org.springframework.context.annotation.Primary;
3536
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
3637
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
3738
import org.springframework.data.jdbc.testing.TestConfiguration;
@@ -105,6 +106,7 @@ SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
105106
}
106107

107108
@Bean
109+
@Primary
108110
MyBatisDataAccessStrategy dataAccessStrategy(SqlSession sqlSession) {
109111

110112
MyBatisDataAccessStrategy strategy = new MyBatisDataAccessStrategy(sqlSession);

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/mybatis/MyBatisHsqlIntegrationTests.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.factory.annotation.Autowired;
3131
import org.springframework.context.annotation.Bean;
3232
import org.springframework.context.annotation.Import;
33+
import org.springframework.context.annotation.Primary;
3334
import org.springframework.data.jdbc.core.DataAccessStrategy;
3435
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
3536
import org.springframework.data.jdbc.testing.TestConfiguration;
@@ -88,8 +89,10 @@ SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
8889
}
8990

9091
@Bean
92+
@Primary
9193
DataAccessStrategy dataAccessStrategy(RelationalMappingContext context, RelationalConverter converter,
9294
SqlSession sqlSession, EmbeddedDatabase db) {
95+
9396
return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, converter,
9497
new NamedParameterJdbcTemplate(db), sqlSession);
9598
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ public class EnableJdbcRepositoriesIntegrationTests {
7979

8080
@BeforeClass
8181
public static void setup() {
82+
8283
MAPPER_MAP.setAccessible(true);
8384
OPERATIONS.setAccessible(true);
8485
DATA_ACCESS_STRATEGY.setAccessible(true);
@@ -105,8 +106,9 @@ public void customRowMapperConfigurationGetsPickedUp() {
105106

106107
@Test // DATAJDBC-293
107108
public void jdbcOperationsRef() {
109+
108110
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS, factoryBean);
109-
assertThat(operations).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierJdbcOperations);
111+
assertThat(operations).isNotSameAs(defaultOperations).isSameAs(qualifierJdbcOperations);
110112

111113
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, factoryBean);
112114
assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy);

0 commit comments

Comments
 (0)