DATAJDBC-293 @EnableJdbcRepositories supports multiple JdbcTemplates.
Original pull request: #102.
This commit is contained in:
@@ -34,6 +34,7 @@ import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBea
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
* @author Mark Paluch
|
||||
* @author Fei Dong
|
||||
* @see JdbcConfiguration
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@@ -98,4 +99,17 @@ public @interface EnableJdbcRepositories {
|
||||
* for {@code PersonRepositoryImpl}.
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link NamedParameterJdbcOperations} bean definition to be used to create repositories
|
||||
* discovered through this annotation. Defaults to {@code namedParameterJdbcTemplate}.
|
||||
*/
|
||||
String jdbcOperationsRef() default "";
|
||||
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link DataAccessStrategy} bean definition to be used to create repositories
|
||||
* discovered through this annotation. Defaults to {@code defaultDataAccessStrategy} if existed.
|
||||
*/
|
||||
String dataAccessStrategyRef() default "";
|
||||
}
|
||||
|
||||
@@ -15,19 +15,41 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.repository.config.RepositoryConfigurationExtension} extending the repository
|
||||
* registration process by registering JDBC repositories.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Fei Dong
|
||||
*/
|
||||
public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
private ConfigurableListableBeanFactory listableBeanFactory;
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName()
|
||||
@@ -36,6 +58,7 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
|
||||
public String getModuleName() {
|
||||
return "JDBC";
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -55,4 +78,87 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
|
||||
return getModuleName().toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#registerBeansForRoot(org.springframework.beans.factory.support.BeanDefinitionRegistry, org.springframework.data.repository.config.RepositoryConfigurationSource)
|
||||
*/
|
||||
public void registerBeansForRoot(BeanDefinitionRegistry registry,
|
||||
RepositoryConfigurationSource configurationSource) {
|
||||
if (registry instanceof ConfigurableListableBeanFactory) {
|
||||
this.listableBeanFactory = (ConfigurableListableBeanFactory) registry;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.RepositoryConfigurationSource)
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSource source) {
|
||||
resolveReference(builder, source, "jdbcOperationsRef", "jdbcOperations", NamedParameterJdbcOperations.class,
|
||||
true);
|
||||
resolveReference(builder, source, "dataAccessStrategyRef", "dataAccessStrategy", DataAccessStrategy.class,
|
||||
false);
|
||||
}
|
||||
|
||||
private void resolveReference(BeanDefinitionBuilder builder, RepositoryConfigurationSource source,
|
||||
String attributeName, String propertyName, Class<?> classRef, boolean required) {
|
||||
Optional<String> beanNameRef = source.getAttribute(attributeName).filter(StringUtils::hasText);
|
||||
|
||||
String beanName = beanNameRef.orElseGet(() -> {
|
||||
if (this.listableBeanFactory != null) {
|
||||
List<String> beanNames = Arrays.asList(listableBeanFactory.getBeanNamesForType(classRef));
|
||||
Map<String, BeanDefinition> bdMap = beanNames.stream()
|
||||
.collect(Collectors.toMap(Function.identity(), listableBeanFactory::getBeanDefinition));
|
||||
|
||||
if (beanNames.size() > 1) {
|
||||
// determine primary
|
||||
|
||||
Map<String, BeanDefinition> primaryBdMap = bdMap.entrySet().stream()
|
||||
.filter(e -> e.getValue().isPrimary())
|
||||
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
|
||||
|
||||
Optional<String> primaryBeanName = getSingleBeanName(primaryBdMap.keySet(), classRef,
|
||||
() -> "more than one 'primary' bean found among candidates: " + primaryBdMap.keySet());
|
||||
|
||||
// In Java 11 should use Optional.or()
|
||||
if (primaryBeanName.isPresent()) {
|
||||
return primaryBeanName.get();
|
||||
}
|
||||
|
||||
// determine matchesBeanName
|
||||
|
||||
Optional<String> matchesBeanName = beanNames.stream()
|
||||
.filter(name -> propertyName.equals(name)
|
||||
|| ObjectUtils.containsElement(listableBeanFactory.getAliases(name), propertyName))
|
||||
.findFirst();
|
||||
|
||||
if (matchesBeanName.isPresent()) {
|
||||
return matchesBeanName.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (beanNames.size() == 1) {
|
||||
return beanNames.get(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (beanName != null) {
|
||||
builder.addPropertyReference(propertyName, beanName);
|
||||
} else if (required) {
|
||||
throw new NoSuchBeanDefinitionException(classRef);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<String> getSingleBeanName(Collection<String> beanNames, Class<?> classRef,
|
||||
Supplier<String> errorMessage) {
|
||||
if (beanNames.size() > 1) {
|
||||
throw new NoUniqueBeanDefinitionException(classRef, beanNames.size(), errorMessage.get());
|
||||
}
|
||||
|
||||
return beanNames.stream().findFirst();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
*
|
||||
* @param repositoryInterface must not be {@literal null}.
|
||||
*/
|
||||
JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
|
||||
protected JdbcRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
|
||||
super(repositoryInterface);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,6 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
/**
|
||||
* @param dataAccessStrategy can be {@literal null}.
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
|
||||
this.dataAccessStrategy = dataAccessStrategy;
|
||||
}
|
||||
@@ -123,7 +122,6 @@ public class JdbcRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extend
|
||||
setQueryMappingConfiguration(rowMapperMap);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setJdbcOperations(NamedParameterJdbcOperations operations) {
|
||||
this.operations = operations;
|
||||
}
|
||||
|
||||
@@ -15,37 +15,48 @@
|
||||
*/
|
||||
package org.springframework.data.jdbc.repository.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import lombok.Data;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.SqlGeneratorSource;
|
||||
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
|
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration;
|
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Tests the {@link EnableJdbcRepositories} annotation.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Greg Turnquist
|
||||
* @author Evgeni Dimitrov
|
||||
* @author Fei Dong
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = TestConfiguration.class)
|
||||
@@ -53,16 +64,24 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
|
||||
static final Field MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class,
|
||||
"queryMappingConfiguration");
|
||||
static final Field OPERATIONS = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "operations");
|
||||
static final Field DATA_ACCESS_STRATEGY = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "dataAccessStrategy");
|
||||
public static final RowMapper DUMMY_ENTITY_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final RowMapper STRING_ROW_MAPPER = mock(RowMapper.class);
|
||||
public static final ResultSetExtractor<Integer> INTEGER_RESULT_SET_EXTRACTOR = mock(ResultSetExtractor.class);
|
||||
|
||||
@Autowired JdbcRepositoryFactoryBean factoryBean;
|
||||
@Autowired DummyRepository repository;
|
||||
@Autowired @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations defaultOperations;
|
||||
@Autowired @Qualifier("defaultDataAccessStrategy") DataAccessStrategy defaultDataAccessStrategy;
|
||||
@Autowired @Qualifier("qualifierJdbcOperations") NamedParameterJdbcOperations qualifierJdbcOperations;
|
||||
@Autowired @Qualifier("qualifierDataAccessStrategy") DataAccessStrategy qualifierDataAccessStrategy;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
MAPPER_MAP.setAccessible(true);
|
||||
OPERATIONS.setAccessible(true);
|
||||
DATA_ACCESS_STRATEGY.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-100
|
||||
@@ -84,6 +103,15 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
assertThat(mapping.getRowMapper(DummyEntity.class)).isEqualTo(DUMMY_ENTITY_ROW_MAPPER);
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-293
|
||||
public void jdbcOperationsRef() {
|
||||
NamedParameterJdbcOperations operations = (NamedParameterJdbcOperations) ReflectionUtils.getField(OPERATIONS, factoryBean);
|
||||
assertThat(operations).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierJdbcOperations);
|
||||
|
||||
DataAccessStrategy dataAccessStrategy = (DataAccessStrategy) ReflectionUtils.getField(DATA_ACCESS_STRATEGY, factoryBean);
|
||||
assertThat(dataAccessStrategy).isNotSameAs(defaultDataAccessStrategy).isSameAs(qualifierDataAccessStrategy);
|
||||
}
|
||||
|
||||
interface DummyRepository extends CrudRepository<DummyEntity, Long> {
|
||||
|
||||
}
|
||||
@@ -95,7 +123,8 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
|
||||
@ComponentScan("org.springframework.data.jdbc.testing")
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true,
|
||||
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class))
|
||||
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class),
|
||||
jdbcOperationsRef = "qualifierJdbcOperations", dataAccessStrategyRef = "qualifierDataAccessStrategy")
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -111,5 +140,15 @@ public class EnableJdbcRepositoriesIntegrationTests {
|
||||
.registerRowMapper(String.class, STRING_ROW_MAPPER);
|
||||
}
|
||||
|
||||
@Bean("qualifierJdbcOperations")
|
||||
NamedParameterJdbcOperations qualifierJdbcOperations(DataSource dataSource) {
|
||||
return new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
@Bean("qualifierDataAccessStrategy")
|
||||
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
|
||||
RelationalMappingContext context, RelationalConverter converter) {
|
||||
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter, template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,12 @@ import javax.sql.DataSource;
|
||||
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.jdbc.core.DataAccessStrategy;
|
||||
import org.springframework.data.jdbc.core.DefaultDataAccessStrategy;
|
||||
@@ -47,6 +49,7 @@ import org.springframework.transaction.PlatformTransactionManager;
|
||||
* @author Oliver Gierke
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
* @author Fei Dong
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan // To pick up configuration classes (per activated profile)
|
||||
@@ -57,8 +60,8 @@ public class TestConfiguration {
|
||||
@Autowired(required = false) SqlSessionFactory sqlSessionFactory;
|
||||
|
||||
@Bean
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory(DataAccessStrategy dataAccessStrategy, RelationalMappingContext context,
|
||||
RelationalConverter converter) {
|
||||
JdbcRepositoryFactory jdbcRepositoryFactory(@Qualifier("defaultDataAccessStrategy") DataAccessStrategy dataAccessStrategy,
|
||||
RelationalMappingContext context, RelationalConverter converter) {
|
||||
return new JdbcRepositoryFactory(dataAccessStrategy, context, converter, publisher, namedParameterJdbcTemplate());
|
||||
}
|
||||
|
||||
@@ -73,13 +76,13 @@ public class TestConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
DataAccessStrategy defaultDataAccessStrategy(RelationalMappingContext context, RelationalConverter converter) {
|
||||
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter,
|
||||
namedParameterJdbcTemplate());
|
||||
DataAccessStrategy defaultDataAccessStrategy(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
|
||||
RelationalMappingContext context, RelationalConverter converter) {
|
||||
return new DefaultDataAccessStrategy(new SqlGeneratorSource(context), context, converter,template);
|
||||
}
|
||||
|
||||
@Bean
|
||||
JdbcMappingContext jdbcMappingContext(NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
|
||||
JdbcMappingContext jdbcMappingContext(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template, Optional<NamingStrategy> namingStrategy,
|
||||
CustomConversions conversions) {
|
||||
|
||||
JdbcMappingContext mappingContext = new JdbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
|
||||
|
||||
Reference in New Issue
Block a user