Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* @author Jens Schauder
* @author Greg Turnquist
* @author Mark Paluch
* @author Fei Dong
* @see JdbcConfiguration
*/
@Target(ElementType.TYPE)
Expand Down Expand Up @@ -97,4 +98,17 @@
* 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 "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -36,6 +58,7 @@ public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtens
public String getModuleName() {
return "JDBC";
}


/*
* (non-Javadoc)
Expand All @@ -55,4 +78,87 @@ protected String getModulePrefix() {
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,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);
}

Expand Down Expand Up @@ -96,7 +96,6 @@ protected void setMappingContext(RelationalMappingContext mappingContext) {
/**
* @param dataAccessStrategy can be {@literal null}.
*/
@Autowired(required = false)
public void setDataAccessStrategy(DataAccessStrategy dataAccessStrategy) {
this.dataAccessStrategy = dataAccessStrategy;
}
Expand All @@ -110,7 +109,6 @@ public void setRowMapperMap(RowMapperMap rowMapperMap) {
this.rowMapperMap = rowMapperMap;
}

@Autowired
public void setJdbcOperations(NamedParameterJdbcOperations operations) {
this.operations = operations;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,69 @@
*/
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.RowMapperMap;
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.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 Fei Dong
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfiguration.class)
public class EnableJdbcRepositoriesIntegrationTests {

static final Field ROW_MAPPER_MAP = ReflectionUtils.findField(JdbcRepositoryFactoryBean.class, "rowMapperMap");
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);

@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() {
ROW_MAPPER_MAP.setAccessible(true);
OPERATIONS.setAccessible(true);
DATA_ACCESS_STRATEGY.setAccessible(true);
}

@Test // DATAJDBC-100
Expand All @@ -80,6 +99,15 @@ public void customRowMapperConfigurationGetsPickedUp() {
assertThat(mapping.rowMapperFor(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> {

}
Expand All @@ -90,7 +118,9 @@ static class DummyEntity {
}

@ComponentScan("org.springframework.data.jdbc.testing")
@EnableJdbcRepositories(considerNestedRepositories = true, includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class))
@EnableJdbcRepositories(considerNestedRepositories = true,
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = DummyRepository.class),
jdbcOperationsRef = "qualifierJdbcOperations", dataAccessStrategyRef = "qualifierDataAccessStrategy")
static class TestConfiguration {

@Bean
Expand All @@ -105,5 +135,15 @@ RowMapperMap rowMappers() {
.register(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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

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;
Expand All @@ -46,6 +48,7 @@
* @author Oliver Gierke
* @author Jens Schauder
* @author Mark Paluch
* @author Fei Dong
*/
@Configuration
@ComponentScan // To pick up configuration classes (per activated profile)
Expand All @@ -56,8 +59,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());
}

Expand All @@ -72,13 +75,13 @@ PlatformTransactionManager transactionManager() {
}

@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
RelationalMappingContext jdbcMappingContext(NamedParameterJdbcOperations template,
RelationalMappingContext jdbcMappingContext(@Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
Optional<NamingStrategy> namingStrategy, CustomConversions conversions) {

RelationalMappingContext mappingContext = new RelationalMappingContext(
Expand Down