DATAJDBC-293 @EnableJdbcRepositories supports multiple JdbcTemplates.

Original pull request: #102.
This commit is contained in:
Jens Schauder
2018-12-14 12:46:17 +01:00
parent df8bedd033
commit 3061b41012
5 changed files with 174 additions and 14 deletions

View File

@@ -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 "";
}

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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));