From b05886e4429d72653222061888223b043b477119 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 11 Apr 2017 12:43:53 +0200 Subject: [PATCH] DATAJDBC-100 - @EnableJdbcRepositories. Adds the @EnableJdbcRepositories annotation, which when used on a Spring configuration enables scanning packages for JDBC repositories. Original pull request: #6. --- .../config/EnableJdbcRepositories.java | 98 ++++++++ .../config/JdbcRepositoriesRegistrar.java | 49 ++++ .../config/JdbcRepositoryConfigExtension.java | 59 +++++ .../support/JdbcRepositoryFactoryBean.java | 100 +++++++++ ...epositoryIdGenerationIntegrationTests.java | 46 +++- ...nableJdbcRepositoriesIntegrationTests.java | 74 +++++++ .../JdbcRepositoryFactoryBeanUnitTests.java | 209 ++++++++++++++++++ ...EnableJdbcRepositoriesIntegrationTests.sql | 1 + 8 files changed, 626 insertions(+), 10 deletions(-) create mode 100644 src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoriesRegistrar.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java create mode 100644 src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java create mode 100644 src/test/resources/EnableJdbcRepositoriesIntegrationTests.sql diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java new file mode 100644 index 000000000..e822e8766 --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositories.java @@ -0,0 +1,98 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.Import; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; + +/** + * Annotation to enable JDBC repositories. Will scan the package of the annotated configuration class for Spring Data + * repositories by default. + * + * @author Jens Schauder + * @since 2.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@Import(JdbcRepositoriesRegistrar.class) +public @interface EnableJdbcRepositories { + + /** + * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.: + * {@code @EnableJpaRepositories("org.my.pkg")} instead of {@code @EnableJpaRepositories(basePackages="org.my.pkg")}. + */ + String[] value() default {}; + + /** + * Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this + * attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names. + */ + String[] basePackages() default {}; + + /** + * Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The + * package of each class specified will be scanned. Consider creating a special no-op marker class or interface in + * each package that serves no purpose other than being referenced by this attribute. + */ + Class[] basePackageClasses() default {}; + + /** + * Specifies which types are eligible for component scanning. Further narrows the set of candidate components from + * everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters. + */ + Filter[] includeFilters() default {}; + + /** + * Specifies which types are not eligible for component scanning. + */ + Filter[] excludeFilters() default {}; + + /** + * Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the + * repositories infrastructure. + */ + boolean considerNestedRepositories() default false; + + /** + * Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to + * {@link JdbcRepositoryFactoryBean}. + */ + Class repositoryFactoryBeanClass() default JdbcRepositoryFactoryBean.class; + + /** + * Configures the location of where to find the Spring Data named queries properties file. Will default to + * {@code META-INF/jdbc-named-queries.properties}. + */ + String namedQueriesLocation() default ""; + + /** + * Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So + * for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning + * for {@code PersonRepositoryImpl}. + */ + String repositoryImplementationPostfix() default "Impl"; +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoriesRegistrar.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoriesRegistrar.java new file mode 100644 index 000000000..25ce8d61b --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoriesRegistrar.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.config; + +import java.lang.annotation.Annotation; + +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport; +import org.springframework.data.repository.config.RepositoryConfigurationExtension; + +/** + * {@link ImportBeanDefinitionRegistrar} to enable {@link EnableJdbcRepositories} annotation. + * + * @author Jens Schauder + * @since 2.0 + */ +class JdbcRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation() + */ + @Override + protected Class getAnnotation() { + return EnableJdbcRepositories.class; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension() + */ + @Override + protected RepositoryConfigurationExtension getExtension() { + return new JdbcRepositoryConfigExtension(); + } +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java new file mode 100644 index 000000000..a8e8940fa --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/config/JdbcRepositoryConfigExtension.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.config; + +import java.util.Locale; + +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; +import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; + +/** + * {@link org.springframework.data.repository.config.RepositoryConfigurationExtension} extending the repository + * registration process by registering JDBC repositories. + * + * @author Jens Schauder + * @since 2.0 + */ +public class JdbcRepositoryConfigExtension extends RepositoryConfigurationExtensionSupport { + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getModuleName() + */ + @Override + public String getModuleName() { + return "JDBC"; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getRepositoryFactoryBeanClassName() + */ + @Override + public String getRepositoryFactoryBeanClassName() { + return JdbcRepositoryFactoryBean.class.getName(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix() + */ + @Override + protected String getModulePrefix() { + return getModuleName().toLowerCase(Locale.US); + } + +} diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java new file mode 100644 index 000000000..0e8dc4e6e --- /dev/null +++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -0,0 +1,100 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.support; + +import java.io.Serializable; +import java.util.Map; +import java.util.Optional; + +import javax.sql.DataSource; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; +import org.springframework.data.util.Optionals; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +/** + * Special adapter for Springs {@link org.springframework.beans.factory.FactoryBean} interface to allow easy setup of + * repository factories via Spring configuration. + * + * @author Jens Schauder + * @since 2.0 + */ +public class JdbcRepositoryFactoryBean, S, ID extends Serializable> // + extends TransactionalRepositoryFactoryBeanSupport { + + private static final String NO_NAMED_PARAMETER_JDBC_OPERATION_ERROR_MESSAGE = // + "No unique NamedParameterJdbcOperation could be found, " // + + "nor JdbcOperations or DataSource to construct one from."; + + private static final String NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME = "namedParameterJdbcTemplate"; + private static final String JDBC_OPERATIONS_BEAN_NAME = "jdbcTemplate"; + private static final String DATA_SOURCE_BEAN_NAME = "dataSource"; + + private final ApplicationEventPublisher applicationEventPublisher; + private final ApplicationContext context; + + JdbcRepositoryFactoryBean(Class repositoryInterface, ApplicationEventPublisher applicationEventPublisher, + ApplicationContext context) { + + super(repositoryInterface); + this.applicationEventPublisher = applicationEventPublisher; + this.context = context; + } + + @Override + protected RepositoryFactorySupport doCreateRepositoryFactory() { + return new JdbcRepositoryFactory(findOrCreateJdbcOperations(), applicationEventPublisher); + } + + private NamedParameterJdbcOperations findOrCreateJdbcOperations() { + + return Optionals + .firstNonEmpty( // + this::getNamedParameterJdbcOperations, // + () -> getJdbcOperations().map(NamedParameterJdbcTemplate::new), // + () -> getDataSource().map(NamedParameterJdbcTemplate::new)) // + .orElseThrow(() -> new IllegalStateException(NO_NAMED_PARAMETER_JDBC_OPERATION_ERROR_MESSAGE)); + } + + private Optional getNamedParameterJdbcOperations() { + return getBean(NamedParameterJdbcOperations.class, NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME); + } + + private Optional getJdbcOperations() { + return getBean(JdbcOperations.class, JDBC_OPERATIONS_BEAN_NAME); + } + + private Optional getDataSource() { + return getBean(DataSource.class, DATA_SOURCE_BEAN_NAME); + } + + private Optional getBean(Class type, String name) { + + Map beansOfType = context.getBeansOfType(type); + + if (beansOfType.size() == 1) { + return beansOfType.values().stream().findFirst(); + } + + return Optional.ofNullable(beansOfType.get(name)); + } +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java index 2c0ab66bb..230ad6d1a 100644 --- a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java +++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java @@ -16,20 +16,25 @@ package org.springframework.data.jdbc.repository; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import lombok.Data; import lombok.Value; +import javax.sql.DataSource; + import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +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.Import; import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; -import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.context.ContextConfiguration; @@ -42,6 +47,7 @@ import org.springframework.test.context.junit4.rules.SpringMethodRule; * @author Jens Schauder */ @ContextConfiguration +@EnableJdbcRepositories(considerNestedRepositories = true) public class JdbcRepositoryIdGenerationIntegrationTests { @Configuration @@ -55,15 +61,6 @@ public class JdbcRepositoryIdGenerationIntegrationTests { return JdbcRepositoryIdGenerationIntegrationTests.class; } - @Bean - ReadOnlyIdEntityRepository readOnlyIdRepository() { - return factory.getRepository(ReadOnlyIdEntityRepository.class); - } - - @Bean - PrimitiveIdEntityRepository primitiveIdRepository() { - return factory.getRepository(PrimitiveIdEntityRepository.class); - } } @ClassRule public static final SpringClassRule classRule = new SpringClassRule(); @@ -121,4 +118,33 @@ public class JdbcRepositoryIdGenerationIntegrationTests { @Id private final long id; String name; } + + @Configuration + @ComponentScan("org.springframework.data.jdbc.testing") + public static class TestConfiguration { + + @Bean + Class testClass() { + return JdbcRepositoryIdGenerationIntegrationTests.class; + } + + @Bean + NamedParameterJdbcTemplate template(DataSource db) { + return new NamedParameterJdbcTemplate(db); + } + + @Bean + ReadOnlyIdEntityRepository readOnlyIdRepository(DataSource db) { + + return new JdbcRepositoryFactory(new NamedParameterJdbcTemplate(db), mock(ApplicationEventPublisher.class)) + .getRepository(ReadOnlyIdEntityRepository.class); + } + + @Bean + PrimitiveIdEntityRepository primitiveIdRepository(NamedParameterJdbcTemplate template) { + + return new JdbcRepositoryFactory(template, mock(ApplicationEventPublisher.class)) + .getRepository(PrimitiveIdEntityRepository.class); + } + } } diff --git a/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java new file mode 100644 index 000000000..cff303f9a --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/config/EnableJdbcRepositoriesIntegrationTests.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.config; + +import static org.junit.Assert.*; + +import lombok.Data; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.data.annotation.Id; +import org.springframework.data.jdbc.repository.JdbcRepositoryIntegrationTests; +import org.springframework.data.jdbc.repository.config.EnableJdbcRepositoriesIntegrationTests.TestConfiguration; +import org.springframework.data.repository.CrudRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Tests the {@link EnableJdbcRepositories} annotation. + * + * @author Jens Schauder + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = TestConfiguration.class) +public class EnableJdbcRepositoriesIntegrationTests { + + @Autowired DummyRepository repository; + + @Test // DATAJDBC-100 + public void repositoryGetsPickedUp() { + + assertNotNull(repository); + + Iterable all = repository.findAll(); + + assertNotNull(all); + } + + interface DummyRepository extends CrudRepository { + + } + + @Data + static class DummyEntity { + @Id private Long id; + } + + @ComponentScan("org.springframework.data.jdbc.testing") + @EnableJdbcRepositories(considerNestedRepositories = true) + static class TestConfiguration { + + @Bean + Class testClass() { + return JdbcRepositoryIntegrationTests.class; + } + } + +} diff --git a/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java new file mode 100644 index 000000000..b5211825f --- /dev/null +++ b/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBeanUnitTests.java @@ -0,0 +1,209 @@ +package org.springframework.data.jdbc.repository.support; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.util.ReflectionTestUtils.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Predicate; + +import javax.sql.DataSource; + +import org.assertj.core.api.Condition; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.annotation.Id; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; + +/** + * Tests the dependency injection for {@link JdbcRepositoryFactoryBean}. + * + * @author Jens Schauder + */ +public class JdbcRepositoryFactoryBeanUnitTests { + + static final String JDBC_OPERATIONS_FIELD_NAME = "jdbcOperations"; + static final String EXPECTED_JDBC_OPERATIONS_BEAN_NAME = "jdbcTemplate"; + static final String EXPECTED_NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME = "namedParameterJdbcTemplate"; + + ApplicationEventPublisher eventPublisher = mock(ApplicationEventPublisher.class); + ApplicationContext context = mock(ApplicationContext.class); + + Map dataSources = new HashMap<>(); + Map jdbcOperations = new HashMap<>(); + Map namedJdbcOperations = new HashMap<>(); + + { + + when(context.getBeansOfType(DataSource.class)).thenReturn(dataSources); + when(context.getBeansOfType(JdbcOperations.class)).thenReturn(jdbcOperations); + when(context.getBeansOfType(NamedParameterJdbcOperations.class)).thenReturn(namedJdbcOperations); + } + + @Test // DATAJDBC-100 + public void exceptionWithUsefulMessage() { + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThatExceptionOfType(IllegalStateException.class) // + .isThrownBy(() -> factoryBean.doCreateRepositoryFactory()); + + } + + @Test // DATAJDBC-100 + public void singleDataSourceGetsUsedForCreatingRepositoryFactory() { + + DataSource expectedDataSource = mock(DataSource.class); + dataSources.put("arbitraryName", expectedDataSource); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedDataSource)); + } + + @Test // DATAJDBC-100 + public void multipleDataSourcesGetDisambiguatedByName() { + + DataSource expectedDataSource = mock(DataSource.class); + dataSources.put("dataSource", expectedDataSource); + dataSources.put("arbitraryName", mock(DataSource.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedDataSource)); + } + + @Test // DATAJDBC-100 + public void singleJdbcOperationsUsedForCreatingRepositoryFactory() { + + JdbcOperations expectedOperations = mock(JdbcOperations.class); + jdbcOperations.put("arbitraryName", expectedOperations); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void multipleJdbcOperationsGetDisambiguatedByName() { + + JdbcOperations expectedOperations = mock(JdbcOperations.class); + jdbcOperations.put(EXPECTED_JDBC_OPERATIONS_BEAN_NAME, expectedOperations); + jdbcOperations.put("arbitraryName", mock(JdbcOperations.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void singleNamedJdbcOperationsUsedForCreatingRepositoryFactory() { + + NamedParameterJdbcOperations expectedOperations = mock(NamedParameterJdbcOperations.class); + namedJdbcOperations.put("arbitraryName", expectedOperations); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void multipleNamedJdbcOperationsGetDisambiguatedByName() { + + NamedParameterJdbcOperations expectedOperations = mock(NamedParameterJdbcOperations.class); + namedJdbcOperations.put(EXPECTED_NAMED_PARAMETER_JDBC_OPERATIONS_BEAN_NAME, expectedOperations); + namedJdbcOperations.put("arbitraryName", mock(NamedParameterJdbcOperations.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void namedParameterJdbcOperationsTakePrecedenceOverDataSource() { + + NamedParameterJdbcOperations expectedOperations = mock(NamedParameterJdbcOperations.class); + namedJdbcOperations.put("arbitraryName", expectedOperations); + dataSources.put("arbitraryName", mock(DataSource.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void jdbcOperationsTakePrecedenceOverDataSource() { + + JdbcOperations expectedOperations = mock(JdbcOperations.class); + jdbcOperations.put("arbitraryName", expectedOperations); + dataSources.put("arbitraryName", mock(DataSource.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + @Test // DATAJDBC-100 + public void namedParameterJdbcOperationsTakePrecedenceOverJdbcOperations() { + + NamedParameterJdbcOperations expectedOperations = mock(NamedParameterJdbcOperations.class); + namedJdbcOperations.put("arbitraryName", expectedOperations); + jdbcOperations.put("arbitraryName", mock(JdbcOperations.class)); + + JdbcRepositoryFactoryBean factoryBean = // + new JdbcRepositoryFactoryBean<>(DummyEntityRepository.class, eventPublisher, context); + + assertThat(factoryBean.doCreateRepositoryFactory()).is(using(expectedOperations)); + } + + private Condition using(NamedParameterJdbcOperations expectedOperations) { + + Predicate predicate = r -> getField(r, JDBC_OPERATIONS_FIELD_NAME) == expectedOperations; + return new Condition<>(predicate, "uses " + expectedOperations); + } + + private Condition using(JdbcOperations expectedOperations) { + + Predicate predicate = r -> { + NamedParameterJdbcOperations namedOperations = (NamedParameterJdbcOperations) getField(r, + JDBC_OPERATIONS_FIELD_NAME); + return namedOperations.getJdbcOperations() == expectedOperations; + }; + + return new Condition<>(predicate, "uses " + expectedOperations); + } + + private Condition using(DataSource expectedDataSource) { + + Predicate predicate = r -> { + + NamedParameterJdbcOperations namedOperations = (NamedParameterJdbcOperations) getField(r, + JDBC_OPERATIONS_FIELD_NAME); + JdbcTemplate jdbcOperations = (JdbcTemplate) namedOperations.getJdbcOperations(); + return jdbcOperations.getDataSource() == expectedDataSource; + }; + + return new Condition<>(predicate, "using " + expectedDataSource); + } + + private static class DummyEntity { + @Id private Long id; + } + + private interface DummyEntityRepository extends CrudRepository {} +} diff --git a/src/test/resources/EnableJdbcRepositoriesIntegrationTests.sql b/src/test/resources/EnableJdbcRepositoriesIntegrationTests.sql new file mode 100644 index 000000000..f0484b5d9 --- /dev/null +++ b/src/test/resources/EnableJdbcRepositoriesIntegrationTests.sql @@ -0,0 +1 @@ +CREATE TABLE dummyentity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY) \ No newline at end of file