diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index d5d90e89defa..85dacb716497 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -28,13 +28,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar; import org.springframework.boot.autoconfigure.jdbc.metadata.DataSourcePoolMetadataProvidersConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -56,16 +53,10 @@ @Configuration @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @EnableConfigurationProperties(DataSourceProperties.class) -@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) +@Import({ DataSourcePoolMetadataProvidersConfiguration.class, + DataSourceInitializationConfiguration.class }) public class DataSourceAutoConfiguration { - @Bean - @ConditionalOnMissingBean - public DataSourceInitializer dataSourceInitializer(DataSourceProperties properties, - ApplicationContext applicationContext) { - return new DataSourceInitializer(properties, applicationContext); - } - @Configuration @Conditional(EmbeddedDatabaseCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java new file mode 100644 index 000000000000..96d064a0cd26 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2012-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.boot.autoconfigure.jdbc; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +/** + * Configures DataSource initialization. + * + * @author Stephane Nicoll + */ +@Configuration +@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class }) +class DataSourceInitializationConfiguration { + + /** + * {@link ImportBeanDefinitionRegistrar} to register the + * {@link DataSourceInitializerPostProcessor} without causing early bean instantiation + * issues. + */ + static class Registrar implements ImportBeanDefinitionRegistrar { + + private static final String BEAN_NAME = "dataSourceInitializerPostProcessor"; + + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, + BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(BEAN_NAME)) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class); + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + // We don't need this one to be post processed otherwise it can cause a + // cascade of bean instantiation that we would rather avoid. + beanDefinition.setSynthetic(true); + registry.registerBeanDefinition(BEAN_NAME, beanDefinition); + } + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java index e177e9a96c30..5d844eeb46e4 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.List; -import javax.annotation.PostConstruct; import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -28,104 +27,100 @@ import org.springframework.boot.context.config.ResourceNotFoundException; import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.jdbc.config.SortedResourcesFactoryBean; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.util.StringUtils; /** - * Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on - * {@link PostConstruct} and {@literal data-*.sql} SQL scripts on a - * {@link DataSourceInitializedEvent}. + * Initialize a {@link DataSource} based on a matching {@link DataSourceProperties} + * config. * * @author Dave Syer * @author Phillip Webb * @author EddĂș MelĂ©ndez * @author Stephane Nicoll * @author Kazuki Shimizu - * @see DataSourceAutoConfiguration */ -class DataSourceInitializer implements ApplicationListener { +class DataSourceInitializer { private static final Log logger = LogFactory.getLog(DataSourceInitializer.class); - private final DataSourceProperties properties; + private final DataSource dataSource; - private final ApplicationContext applicationContext; + private final DataSourceProperties properties; - private DataSource dataSource; + private final ResourceLoader resourceLoader; + + /** + * Create a new instance with the {@link DataSource} to initialize and its matching + * {@link DataSourceProperties configuration}. + * @param dataSource the datasource to initialize + * @param properties the matching configuration + * @param resourceLoader the resource loader to use (can be null) + */ + DataSourceInitializer(DataSource dataSource, DataSourceProperties properties, + ResourceLoader resourceLoader) { + this.dataSource = dataSource; + this.properties = properties; + this.resourceLoader = (resourceLoader != null ? resourceLoader + : new DefaultResourceLoader()); + } - private boolean initialized = false; + /** + * Create a new instance with the {@link DataSource} to initialize and its matching + * {@link DataSourceProperties configuration}. + * @param dataSource the datasource to initialize + * @param properties the matching configuration + */ + DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) { + this(dataSource, properties, null); + } - DataSourceInitializer(DataSourceProperties properties, - ApplicationContext applicationContext) { - this.properties = properties; - this.applicationContext = applicationContext; + public DataSource getDataSource() { + return this.dataSource; } - @PostConstruct - public void init() { + /** + * Create the schema if necessary. + * @return {@code true} if the schema was created + * @see DataSourceProperties#getSchema() + */ + public boolean createSchema() { if (!this.properties.isInitialize()) { logger.debug("Initialization disabled (not running DDL scripts)"); - return; - } - if (this.applicationContext.getBeanNamesForType(DataSource.class, false, - false).length > 0) { - this.dataSource = this.applicationContext.getBean(DataSource.class); + return false; } - if (this.dataSource == null) { - logger.debug("No DataSource found so not initializing"); - return; - } - runSchemaScripts(); - } - - private void runSchemaScripts() { List scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema"); if (!scripts.isEmpty()) { String username = this.properties.getSchemaUsername(); String password = this.properties.getSchemaPassword(); runScripts(scripts, username, password); - try { - this.applicationContext - .publishEvent(new DataSourceInitializedEvent(this.dataSource)); - // The listener might not be registered yet, so don't rely on it. - if (!this.initialized) { - runDataScripts(); - this.initialized = true; - } - } - catch (IllegalStateException ex) { - logger.warn("Could not send event to complete DataSource initialization (" - + ex.getMessage() + ")"); - } } + return !scripts.isEmpty(); } - @Override - public void onApplicationEvent(DataSourceInitializedEvent event) { + /** + * Initialize the schema if necessary. + * @see DataSourceProperties#getData() + */ + public void initSchema() { if (!this.properties.isInitialize()) { logger.debug("Initialization disabled (not running data scripts)"); return; } - // NOTE the event can happen more than once and - // the event datasource is not used here - if (!this.initialized) { - runDataScripts(); - this.initialized = true; - } - } - - private void runDataScripts() { List scripts = getScripts("spring.datasource.data", this.properties.getData(), "data"); - String username = this.properties.getDataUsername(); - String password = this.properties.getDataPassword(); - runScripts(scripts, username, password); + if (!scripts.isEmpty()) { + String username = this.properties.getDataUsername(); + String password = this.properties.getDataPassword(); + runScripts(scripts, username, password); + + } } private List getScripts(String propertyName, List resources, @@ -159,7 +154,7 @@ else if (validate) { private Resource[] doGetResources(String location) { try { SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean( - this.applicationContext, Collections.singletonList(location)); + this.resourceLoader, Collections.singletonList(location)); factory.afterPropertiesSet(); return factory.getObject(); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvoker.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvoker.java new file mode 100644 index 000000000000..112c4c040bbc --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvoker.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-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.boot.autoconfigure.jdbc; + +import javax.sql.DataSource; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; + +/** + * Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on + * {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on + * a {@link DataSourceInitializedEvent}. + * + * @author Stephane Nicoll + * @see DataSourceAutoConfiguration + */ +class DataSourceInitializerInvoker + implements ApplicationListener, InitializingBean { + + private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class); + + private final ObjectProvider dataSource; + + private final DataSourceProperties properties; + + private final ApplicationContext applicationContext; + + private DataSourceInitializer dataSourceInitializer; + + private boolean initialized; + + DataSourceInitializerInvoker(ObjectProvider dataSource, + DataSourceProperties properties, + ApplicationContext applicationContext) { + this.dataSource = dataSource; + this.properties = properties; + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() { + DataSourceInitializer initializer = getDataSourceInitializer(); + if (initializer != null) { + boolean schemaCreated = this.dataSourceInitializer.createSchema(); + if (schemaCreated) { + try { + this.applicationContext + .publishEvent(new DataSourceInitializedEvent( + initializer.getDataSource())); + // The listener might not be registered yet, so don't rely on it. + if (!this.initialized) { + this.dataSourceInitializer.initSchema(); + this.initialized = true; + } + } + catch (IllegalStateException ex) { + logger.warn("Could not send event to complete DataSource initialization (" + + ex.getMessage() + ")"); + } + } + } + } + + @Override + public void onApplicationEvent(DataSourceInitializedEvent event) { + // NOTE the event can happen more than once and + // the event datasource is not used here + DataSourceInitializer initializer = getDataSourceInitializer(); + if (!this.initialized && initializer != null) { + initializer.initSchema(); + this.initialized = true; + } + } + + private DataSourceInitializer getDataSourceInitializer() { + if (this.dataSourceInitializer == null) { + DataSource ds = this.dataSource.getIfUnique(); + if (ds != null) { + this.dataSourceInitializer = new DataSourceInitializer(ds, + this.properties, this.applicationContext); + } + } + return this.dataSourceInitializer; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.java index 74046ea10d5e..92426b200867 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-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. @@ -21,17 +21,12 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.Ordered; -import org.springframework.core.type.AnnotationMetadata; /** - * {@link BeanPostProcessor} used to fire {@link DataSourceInitializedEvent}s. Should only - * be registered via the inner {@link Registrar} class. + * {@link BeanPostProcessor} used to ensure that {@link DataSourceInitializer} is + * initialized as soon as a {@link DataSource} is. * * @author Dave Syer * @since 1.1.2 @@ -59,34 +54,9 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof DataSource) { // force initialization of this bean as soon as we see a DataSource - this.beanFactory.getBean(DataSourceInitializer.class); + this.beanFactory.getBean(DataSourceInitializerInvoker.class); } return bean; } - /** - * {@link ImportBeanDefinitionRegistrar} to register the - * {@link DataSourceInitializerPostProcessor} without causing early bean instantiation - * issues. - */ - static class Registrar implements ImportBeanDefinitionRegistrar { - - private static final String BEAN_NAME = "dataSourceInitializerPostProcessor"; - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, - BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(BEAN_NAME)) { - GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); - beanDefinition.setBeanClass(DataSourceInitializerPostProcessor.class); - beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - // We don't need this one to be post processed otherwise it can cause a - // cascade of bean instantiation that we would rather avoid. - beanDefinition.setSynthetic(true); - registry.registerBeanDefinition(BEAN_NAME, beanDefinition); - } - } - - } - } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvokerTests.java similarity index 75% rename from spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java rename to spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvokerTests.java index 86bc6ac669e2..88c2b004de63 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvokerTests.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Random; +import java.util.UUID; import javax.sql.DataSource; @@ -29,15 +30,13 @@ import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; @@ -57,50 +56,49 @@ * @author Dave Syer * @author Stephane Nicoll */ -public class DataSourceInitializerTests { +public class DataSourceInitializerInvokerTests { private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(BasicConfiguration.class) + .withConfiguration( + AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize=false", - "spring.datasource.url:jdbc:hsqldb:mem:testdb-" - + new Random().nextInt()); + "spring.datasource.url:jdbc:hsqldb:mem:init-" + + UUID.randomUUID().toString()); @Test - public void defaultDataSourceDoesNotExists() { + public void dataSourceInitialized() { this.contextRunner - .run((context) -> assertThat(context).doesNotHaveBean(DataSource.class)); + .withPropertyValues("spring.datasource.initialize:true") + .run((context) -> { + assertThat(context).hasSingleBean(DataSource.class); + DataSource dataSource = context.getBean(DataSource.class); + assertThat(dataSource).isInstanceOf(HikariDataSource.class); + assertDataSourceIsInitialized(dataSource); + }); } - @Test - public void twoDataSources() { - this.contextRunner.withUserConfiguration(TwoDataSources.class) - .withPropertyValues("datasource.one.url=jdbc:hsqldb:mem:/one", - "datasource.two.url=jdbc:hsqldb:mem:/two") - .run((context) -> assertThat( - context.getBeanNamesForType(DataSource.class)).hasSize(2)); - } @Test - public void dataSourceInitialized() { + public void initializationAppliesToCustomDataSource() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withUserConfiguration(OneDataSource.class) .withPropertyValues("spring.datasource.initialize:true") .run((context) -> { - DataSource dataSource = context.getBean(DataSource.class); - assertThat(dataSource).isInstanceOf(HikariDataSource.class); - assertThat(dataSource).isNotNull(); - JdbcOperations template = new JdbcTemplate(dataSource); - assertThat(template.queryForObject("SELECT COUNT(*) from BAR", - Integer.class)).isEqualTo(1); + assertThat(context).hasSingleBean(DataSource.class); + assertDataSourceIsInitialized(context.getBean(DataSource.class)); }); } + + private void assertDataSourceIsInitialized(DataSource dataSource) { + JdbcOperations template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject("SELECT COUNT(*) from BAR", + Integer.class)).isEqualTo(1); + } + @Test public void dataSourceInitializedWithExplicitScript() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.schema:" + getRelativeLocationFor("schema.sql"), @@ -118,8 +116,6 @@ public void dataSourceInitializedWithExplicitScript() { @Test public void dataSourceInitializedWithMultipleScripts() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.schema:" + getRelativeLocationFor("schema.sql") + "," + getRelativeLocationFor("another.sql"), @@ -139,8 +135,6 @@ public void dataSourceInitializedWithMultipleScripts() { @Test public void dataSourceInitializedWithExplicitSqlScriptEncoding() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.sqlScriptEncoding:UTF-8", "spring.datasource.schema:" @@ -163,34 +157,50 @@ public void dataSourceInitializedWithExplicitSqlScriptEncoding() { @Test public void initializationDisabled() { + this.contextRunner.run(assertInitializationIsDisabled()); + } + + @Test + public void initializationDoesNotApplyWithSeveralDataSources() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withUserConfiguration(TwoDataSources.class) + .withPropertyValues("spring.datasource.initialize:true") .run((context) -> { - DataSource dataSource = context.getBean(DataSource.class); - context.publishEvent(new DataSourceInitializedEvent(dataSource)); - assertThat(dataSource).isInstanceOf(HikariDataSource.class); - assertThat(dataSource).isNotNull(); - JdbcOperations template = new JdbcTemplate(dataSource); - try { - template.queryForObject("SELECT COUNT(*) from BAR", - Integer.class); - fail("Query should have failed as BAR table does not exist"); - } - catch (BadSqlGrammarException ex) { - SQLException sqlException = ex.getSQLException(); - int expectedCode = -5501; // user lacks privilege or object not - // found - assertThat(sqlException.getErrorCode()).isEqualTo(expectedCode); - } + assertThat(context.getBeanNamesForType(DataSource.class)).hasSize(2); + assertDataSourceNotInitialized(context.getBean( + "oneDataSource", DataSource.class)); + assertDataSourceNotInitialized(context.getBean( + "twoDataSource", DataSource.class)); }); } + private ContextConsumer assertInitializationIsDisabled() { + return context -> { + assertThat(context).hasSingleBean(DataSource.class); + DataSource dataSource = context.getBean(DataSource.class); + context.publishEvent(new DataSourceInitializedEvent(dataSource)); + assertDataSourceNotInitialized(dataSource); + }; + } + + private void assertDataSourceNotInitialized(DataSource dataSource) { + JdbcOperations template = new JdbcTemplate(dataSource); + try { + template.queryForObject("SELECT COUNT(*) from BAR", + Integer.class); + fail("Query should have failed as BAR table does not exist"); + } + catch (BadSqlGrammarException ex) { + SQLException sqlException = ex.getSQLException(); + int expectedCode = -5501; // user lacks privilege or object not + // found + assertThat(sqlException.getErrorCode()).isEqualTo(expectedCode); + } + } + @Test public void dataSourceInitializedWithSchemaCredentials() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.sqlScriptEncoding:UTF-8", "spring.datasource.schema:" @@ -209,8 +219,6 @@ public void dataSourceInitializedWithSchemaCredentials() { @Test public void dataSourceInitializedWithDataCredentials() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.sqlScriptEncoding:UTF-8", "spring.datasource.schema:" @@ -233,9 +241,7 @@ public void multipleScriptsAppliedInLexicalOrder() { context.setResourceLoader( new ReverseOrderResourceLoader(new DefaultResourceLoader())); return context; - }).withUserConfiguration(BasicConfiguration.class) - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) + }).withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize=false", "spring.datasource.url:jdbc:hsqldb:mem:testdb-" + new Random().nextInt(), @@ -256,8 +262,6 @@ public void multipleScriptsAppliedInLexicalOrder() { @Test public void testDataSourceInitializedWithInvalidSchemaResource() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.schema:classpath:does/not/exist.sql") .run((context) -> { @@ -274,8 +278,6 @@ public void testDataSourceInitializedWithInvalidSchemaResource() { @Test public void dataSourceInitializedWithInvalidDataResource() { this.contextRunner - .withConfiguration( - AutoConfigurations.of(DataSourceAutoConfiguration.class)) .withPropertyValues("spring.datasource.initialize:true", "spring.datasource.schema:" + getRelativeLocationFor("schema.sql"), @@ -296,27 +298,26 @@ private String getRelativeLocationFor(String resource) { } @Configuration - @EnableConfigurationProperties(DataSourceProperties.class) - @Import(DataSourceInitializer.class) - static class BasicConfiguration { - - } - - @Configuration - @EnableConfigurationProperties - protected static class TwoDataSources { + protected static class OneDataSource { @Bean - @Primary - @ConfigurationProperties(prefix = "datasource.one") public DataSource oneDataSource() { - return DataSourceBuilder.create().build(); + return createRandomDataSource(); + } + + protected DataSource createRandomDataSource() { + String url = "jdbc:hsqldb:mem:init-" + UUID.randomUUID().toString(); + return DataSourceBuilder.create().url(url).build(); } + } + + @Configuration + protected static class TwoDataSources extends OneDataSource { + @Bean - @ConfigurationProperties(prefix = "datasource.two") public DataSource twoDataSource() { - return DataSourceBuilder.create().build(); + return createRandomDataSource(); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java index 20d13f518b04..f6e5d2b6fe08 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java @@ -28,7 +28,6 @@ import org.apache.tomcat.jdbc.pool.DataSource; import org.apache.tomcat.jdbc.pool.DataSourceProxy; import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool; -import org.hsqldb.jdbc.JDBCDriver; import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -38,9 +37,6 @@ import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.jmx.export.UnableToRegisterMBeanException; import static org.assertj.core.api.Assertions.assertThat; @@ -63,13 +59,6 @@ public void close() { } } - @Test - public void hikariCustomCannotUseRegisterMBeansByDefault() { - this.thrown.expect(UnableToRegisterMBeanException.class); - this.thrown.expectMessage("myDataSource"); - load(CustomHikariConfiguration.class); - } - @Test public void hikariAutoConfiguredCanUseRegisterMBeans() throws MalformedObjectNameException { @@ -140,18 +129,4 @@ private void load(Class config, String... environment) { this.context = context; } - @Configuration - static class CustomHikariConfiguration { - - @Bean - public HikariDataSource myDataSource() { - HikariDataSource dataSource = new HikariDataSource(); - dataSource.setJdbcUrl("jdbc:hsqldb:mem:custom"); - dataSource.setDriverClassName(JDBCDriver.class.getName()); - dataSource.setRegisterMbeans(true); - return dataSource; - } - - } - }