Skip to content

Commit 3061b41

Browse files
committed
DATAJDBC-293 @EnableJdbcRepositories supports multiple JdbcTemplates.
Original pull request: #102.
1 parent df8bedd commit 3061b41

File tree

5 files changed

+174
-14
lines changed

5 files changed

+174
-14
lines changed

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Jens Schauder
3535
* @author Greg Turnquist
3636
* @author Mark Paluch
37+
* @author Fei Dong
3738
* @see JdbcConfiguration
3839
*/
3940
@Target(ElementType.TYPE)
@@ -98,4 +99,17 @@
9899
* for {@code PersonRepositoryImpl}.
99100
*/
100101
String repositoryImplementationPostfix() default "Impl";
102+
103+
/**
104+
* Configures the name of the {@link NamedParameterJdbcOperations} bean definition to be used to create repositories
105+
* discovered through this annotation. Defaults to {@code namedParameterJdbcTemplate}.
106+
*/
107+
String jdbcOperationsRef() default "";
108+
109+
110+
/**
111+
* Configures the name of the {@link DataAccessStrategy} bean definition to be used to create repositories
112+
* discovered through this annotation. Defaults to {@code defaultDataAccessStrategy} if existed.
113+
*/
114+
String dataAccessStrategyRef() default "";
101115
}

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

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,41 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.config;
1717

18+
import java.util.Arrays;
19+
import java.util.Collection;
20+
import java.util.function.Function;
21+
import java.util.List;
1822
import java.util.Locale;
23+
import java.util.Map;
24+
import java.util.Map.Entry;
25+
import java.util.Optional;
26+
import java.util.function.Supplier;
27+
import java.util.stream.Collectors;
1928

29+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
30+
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
31+
import org.springframework.beans.factory.config.BeanDefinition;
32+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
33+
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
34+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
35+
import org.springframework.data.jdbc.core.DataAccessStrategy;
2036
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
2137
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
38+
import org.springframework.data.repository.config.RepositoryConfigurationSource;
39+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
40+
import org.springframework.util.ObjectUtils;
41+
import org.springframework.util.StringUtils;
2242

2343
/**
2444
* {@link org.springframework.data.repository.config.RepositoryConfigurationExtension} extending the repository
2545
* registration process by registering JDBC repositories.
2646
*
2747
* @author Jens Schauder
48+
* @author Fei Dong
2849
*/
2950
public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
3051

52+
private ConfigurableListableBeanFactory listableBeanFactory;
3153
/*
3254
* (non-Javadoc)
3355
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName()
@@ -36,6 +58,7 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
3658
public String getModuleName() {
3759
return "JDBC";
3860
}
61+
3962

4063
/*
4164
* (non-Javadoc)
@@ -55,4 +78,87 @@ protected String getModulePrefix() {
5578
return getModuleName().toLowerCase(Locale.US);
5679
}
5780

81+
/*
82+
* (non-Javadoc)
83+
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
84+
*/
85+
public void registerBeansForRoot(BeanDefinitionRegistry registry,
86+
RepositoryConfigurationSource configurationSource) {
87+
if (registry instanceof ConfigurableListableBeanFactory) {
88+
this.listableBeanFactory = (ConfigurableListableBeanFactory) registry;
89+
}
90+
}
91+
92+
93+
/*
94+
* (non-Javadoc)
95+
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
96+
*/
97+
@Override
98+
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
99+
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class,
100+
true);
101+
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class,
102+
false);
103+
}
104+
105+
private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source,
106+
String attributeName, String propertyName, Class<?> classRef, boolean required) {
107+
Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText);
108+
109+
String beanName = beanNameRef.orElseGet(() -> {
110+
if (this.listableBeanFactory != null) {
111+
List<String> beanNames = Arrays.asList(listableBeanFactory.getBeanNamesForType(classRef));
112+
Map<String, BeanDefinition> bdMap = beanNames.stream()
113+
.collect(Collectors.toMap(Function.identity(), listableBeanFactory::getBeanDefinition));
114+
115+
if (beanNames.size() > 1) {
116+
// determine primary
117+
118+
Map<String, BeanDefinition> primaryBdMap = bdMap.entrySet().stream()
119+
.filter(e -> e.getValue().isPrimary())
120+
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
121+
122+
Optional<String> primaryBeanName = getSingleBeanName(primaryBdMap.keySet(), classRef,
123+
() -> "more than one 'primary' bean found among candidates: " + primaryBdMap.keySet());
124+
125+
// In Java 11 should use Optional.or()
126+
if (primaryBeanName.isPresent()) {
127+
return primaryBeanName.get();
128+
}
129+
130+
// determine matchesBeanName
131+
132+
Optional<String> matchesBeanName = beanNames.stream()
133+
.filter(name -> propertyName.equals(name)
134+
|| ObjectUtils.containsElement(listableBeanFactory.getAliases(name), propertyName))
135+
.findFirst();
136+
137+
if (matchesBeanName.isPresent()) {
138+
return matchesBeanName.get();
139+
}
140+
141+
}
142+
143+
if (beanNames.size() == 1) {
144+
return beanNames.get(0);
145+
}
146+
}
147+
return null;
148+
});
149+
if (beanName != null) {
150+
builder.addPropertyReference(propertyName, beanName);
151+
} else if (required) {
152+
throw new NoSuchBeanDefinitionException(classRef);
153+
}
154+
}
155+
156+
private Optional<String> getSingleBeanName(Collection<String> beanNames, Class<?> classRef,
157+
Supplier<String> errorMessage) {
158+
if (beanNames.size() > 1) {
159+
throw new NoUniqueBeanDefinitionException(classRef, beanNames.size(), errorMessage.get());
160+
}
161+
162+
return beanNames.stream().findFirst();
163+
}
58164
}

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
5858
*
5959
* @param repositoryInterface must not be {@literal null}.
6060
*/
61-
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
61+
protected JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
6262
super(repositoryInterface);
6363
}
6464

@@ -97,7 +97,6 @@ protected void setMappingContext(RelationalMappingContext mappingContext) {
9797
/**
9898
* @param dataAccessStrategy can be {@literal null}.
9999
*/
100-
@Autowired(required = false)
101100
public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
102101
this.dataAccessStrategy = dataAccessStrategy;
103102
}
@@ -123,7 +122,6 @@ public void setRowMapperMap(RowMapperMap rowMapperMap) {
123122
setQueryMappingConfiguration(rowMapperMap);
124123
}
125124

126-
@Autowired
127125
public void setJdbcOperations(NamedParameterJdbcOperations operations) {
128126
this.operations = operations;
129127
}

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,54 +15,73 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.config;
1717

18-
import static org.assertj.core.api.Assertions.*;
19-
import static org.mockito.Mockito.*;
20-
21-
import lombok.Data;
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.Mockito.mock;
2220

2321
import java.lang.reflect.Field;
2422

23+
import javax.sql.DataSource;
24+
2525
import org.junit.BeforeClass;
2626
import org.junit.Test;
2727
import org.junit.runner.RunWith;
2828
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.beans.factory.annotation.Qualifier;
2930
import org.springframework.context.annotation.Bean;
3031
import org.springframework.context.annotation.ComponentScan;
3132
import org.springframework.context.annotation.FilterType;
3233
import org.springframework.data.annotation.Id;
34+
import org.springframework.data.jdbc.core.DataAccessStrategy;
35+
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
36+
import org.springframework.data.jdbc.core.SqlGeneratorSource;
3337
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
3438
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
3539
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
40+
import org.springframework.data.relational.core.conversion.RelationalConverter;
41+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3642
import org.springframework.data.repository.CrudRepository;
3743
import org.springframework.jdbc.core.ResultSetExtractor;
3844
import org.springframework.jdbc.core.RowMapper;
45+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
46+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
3947
import org.springframework.test.context.ContextConfiguration;
4048
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
4149
import org.springframework.util.ReflectionUtils;
4250

51+
import lombok.Data;
52+
4353
/**
4454
* Tests the {@link EnableJdbcRepositories} annotation.
4555
*
4656
* @author Jens Schauder
4757
* @author Greg Turnquist
4858
* @author Evgeni Dimitrov
59+
* @author Fei Dong
4960
*/
5061
@RunWith(SpringJUnit4ClassRunner.class)
5162
@ContextConfiguration(classes = TestConfiguration.class)
5263
public class EnableJdbcRepositoriesIntegrationTests {
5364

5465
static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
5566
"queryMappingConfiguration");
67+
static final Field OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "operations");
68+
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "dataAccessStrategy");
5669
public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class);
5770
public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class);
5871
public static final ResultSetExtractor<Integer> INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class);
5972

6073
@Autowired JdbcRepositoryFactoryBean factoryBean;
6174
@Autowired DummyRepository repository;
75+
@Autowired @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations defaultOperations;
76+
@Autowired @Qualifier("defaultDataAccessStrategy") DataAccessStrategy defaultDataAccessStrategy;
77+
@Autowired @Qualifier("qualifierJdbcOperations") NamedParameterJdbcOperations qualifierJdbcOperations;
78+
@Autowired @Qualifier("qualifierDataAccessStrategy") DataAccessStrategy qualifierDataAccessStrategy;
6279

6380
@BeforeClass
6481
public static void setup() {
6582
MAPPER_MAP.setAccessible(true);
83+
OPERATIONS.setAccessible(true);
84+
DATA_ACCESS_STRATEGY.setAccessible(true);
6685
}
6786

6887
@Test // DATAJDBC-100
@@ -84,6 +103,15 @@ public void customRowMapperConfigurationGetsPickedUp() {
84103
assertThat(mapping.getRowMapper(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER);
85104
}
86105

106+
@Test // DATAJDBC-293
107+
public void jdbcOperationsRef() {
108+
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS, factoryBean);
109+
assertThat(operations).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierJdbcOperations);
110+
111+
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, factoryBean);
112+
assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy);
113+
}
114+
87115
interface DummyRepository extends CrudRepository<DummyEntity, Long> {
88116

89117
}
@@ -95,7 +123,8 @@ static class DummyEntity {
95123

96124
@ComponentScan("org.springframework.data.jdbc.testing")
97125
@EnableJdbcRepositories(considerNestedRepositories = true,
98-
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class))
126+
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class),
127+
jdbcOperationsRef = "qualifierJdbcOperations", dataAccessStrategyRef = "qualifierDataAccessStrategy")
99128
static class TestConfiguration {
100129

101130
@Bean
@@ -111,5 +140,15 @@ QueryMappingConfiguration rowMappers() {
111140
.registerRowMapper(String.class, STRING_ROW_MAPPER);
112141
}
113142

143+
@Bean("qualifierJdbcOperations")
144+
NamedParameterJdbcOperations qualifierJdbcOperations(DataSource dataSource) {
145+
return new NamedParameterJdbcTemplate(dataSource);
146+
}
147+
148+
@Bean("qualifierDataAccessStrategy")
149+
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
150+
RelationalMappingContext context, RelationalConverter converter) {
151+
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template);
152+
}
114153
}
115154
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121

2222
import org.apache.ibatis.session.SqlSessionFactory;
2323
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.beans.factory.annotation.Qualifier;
2425
import org.springframework.context.ApplicationEventPublisher;
2526
import org.springframework.context.annotation.Bean;
2627
import org.springframework.context.annotation.ComponentScan;
2728
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.context.annotation.Primary;
2830
import org.springframework.data.convert.CustomConversions;
2931
import org.springframework.data.jdbc.core.DataAccessStrategy;
3032
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
@@ -47,6 +49,7 @@
4749
* @author Oliver Gierke
4850
* @author Jens Schauder
4951
* @author Mark Paluch
52+
* @author Fei Dong
5053
*/
5154
@Configuration
5255
@ComponentScan // To pick up configuration classes (per activated profile)
@@ -57,8 +60,8 @@ public class TestConfiguration {
5760
@Autowired(required = false) SqlSessionFactory sqlSessionFactory;
5861

5962
@Bean
60-
JdbcRepositoryFactory jdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
61-
RelationalConverter converter) {
63+
JdbcRepositoryFactory jdbcRepositoryFactory(@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy,
64+
RelationalMappingContext context, RelationalConverter converter) {
6265
return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate());
6366
}
6467

@@ -73,13 +76,13 @@ PlatformTransactionManager transactionManager() {
7376
}
7477

7578
@Bean
76-
DataAccessStrategy defaultDataAccessStrategy(RelationalMappingContext context, RelationalConverter converter) {
77-
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter,
78-
namedParameterJdbcTemplate());
79+
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
80+
RelationalMappingContext context, RelationalConverter converter) {
81+
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter,template);
7982
}
8083

8184
@Bean
82-
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
85+
JdbcMappingContext jdbcMappingContext(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
8386
CustomConversions conversions) {
8487

8588
JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));

0 commit comments

Comments
 (0)