diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java index 800cdb1af9..4410b30f27 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/liquibase/LiquibaseEndpointTests.java @@ -32,8 +32,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.boot.jdbc.init.DataSourceInitializationSettings; -import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer; +import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -148,9 +148,10 @@ class LiquibaseEndpointTests { DataSource dataSource = new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseConnection.get(getClass().getClassLoader()).getType()) .setName(UUID.randomUUID().toString()).build(); - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("classpath:/db/create-custom-schema.sql")); - ScriptDataSourceInitializer initializer = new ScriptDataSourceInitializer(dataSource, settings); + DataSourceScriptDatabaseInitializer initializer = new DataSourceScriptDatabaseInitializer(dataSource, + settings); initializer.initializeDatabase(); return dataSource; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java index e4609776ca..3a5f847b6e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java @@ -41,9 +41,9 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfi import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceInitializationMode; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.boot.jdbc.init.DataSourceInitializationSettings; -import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer; +import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.jdbc.init.dependency.DataSourceInitializationDependencyConfigurer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.DependsOn; @@ -56,7 +56,7 @@ import org.springframework.util.StringUtils; /** * Configuration for {@link DataSource} initialization using a - * {@link ScriptDataSourceInitializer} with DDL and DML scripts. + * {@link DataSourceScriptDatabaseInitializer} with DDL and DML scripts. * * @author Andy Wilkinson */ @@ -87,13 +87,13 @@ class DataSourceInitializationConfiguration { @org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class) @org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class) @ConditionalOnSingleCandidate(DataSource.class) - @ConditionalOnMissingBean(ScriptDataSourceInitializer.class) + @ConditionalOnMissingBean(DataSourceScriptDatabaseInitializer.class) static class InitializationSpecificCredentialsDataSourceInitializationConfiguration { @Bean - ScriptDataSourceInitializer ddlOnlyScriptDataSourceInitializer(ObjectProvider dataSource, + DataSourceScriptDatabaseInitializer ddlOnlyScriptDataSourceInitializer(ObjectProvider dataSource, DataSourceProperties properties, ResourceLoader resourceLoader) { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform())); settings.setContinueOnError(properties.isContinueOnError()); settings.setSeparator(properties.getSeparator()); @@ -106,9 +106,9 @@ class DataSourceInitializationConfiguration { @Bean @DependsOn("ddlOnlyScriptDataSourceInitializer") - ScriptDataSourceInitializer dmlOnlyScriptDataSourceInitializer(ObjectProvider dataSource, + DataSourceScriptDatabaseInitializer dmlOnlyScriptDataSourceInitializer(ObjectProvider dataSource, DataSourceProperties properties, ResourceLoader resourceLoader) { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform())); settings.setContinueOnError(properties.isContinueOnError()); settings.setSeparator(properties.getSeparator()); @@ -144,13 +144,13 @@ class DataSourceInitializationConfiguration { @org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class) @org.springframework.context.annotation.Conditional(DataSourceInitializationCondition.class) @ConditionalOnSingleCandidate(DataSource.class) - @ConditionalOnMissingBean(ScriptDataSourceInitializer.class) + @ConditionalOnMissingBean(DataSourceScriptDatabaseInitializer.class) static class SharedCredentialsDataSourceInitializationConfiguration { @Bean - ScriptDataSourceInitializer scriptDataSourceInitializer(DataSource dataSource, DataSourceProperties properties, - ResourceLoader resourceLoader) { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DataSourceScriptDatabaseInitializer scriptDataSourceInitializer(DataSource dataSource, + DataSourceProperties properties, ResourceLoader resourceLoader) { + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform())); settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform())); settings.setContinueOnError(properties.isContinueOnError()); @@ -188,12 +188,12 @@ class DataSourceInitializationConfiguration { } - static class InitializationModeDataSourceScriptDatabaseInitializer extends ScriptDataSourceInitializer { + static class InitializationModeDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer { private final DataSourceInitializationMode mode; InitializationModeDataSourceScriptDatabaseInitializer(DataSource dataSource, - DataSourceInitializationSettings settings, DataSourceInitializationMode mode) { + DatabaseInitializationSettings settings, DataSourceInitializationMode mode) { super(dataSource, settings); this.mode = mode; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.java new file mode 100644 index 0000000000..6b5cfbc02e --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.sql.init; + +import javax.sql.DataSource; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; +import org.springframework.boot.jdbc.init.dependency.DataSourceInitializationDependencyConfigurer; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; +import org.springframework.util.StringUtils; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class) +@ConditionalOnSingleCandidate(DataSource.class) +@Import(DataSourceInitializationDependencyConfigurer.class) +class DataSourceInitializationConfiguration { + + @Bean + DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource, + SqlInitializationProperties initializationProperties) { + DatabaseInitializationSettings settings = SettingsCreator.createFrom(initializationProperties); + return new DataSourceScriptDatabaseInitializer(determineDataSource(dataSource, + initializationProperties.getUsername(), initializationProperties.getPassword()), settings); + } + + private static DataSource determineDataSource(DataSource dataSource, String username, String password) { + if (StringUtils.hasText(username) && StringUtils.hasText(password)) { + DataSourceBuilder.derivedFrom(dataSource).username(username).password(password) + .type(SimpleDriverDataSource.class).build(); + } + return dataSource; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/R2dbcInitializationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/R2dbcInitializationConfiguration.java new file mode 100644 index 0000000000..e6aee342ed --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/R2dbcInitializationConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.sql.init; + +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; +import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.StringUtils; + +/** + * Configuration for initializing an SQL database accessed via an R2DBC + * {@link ConnectionFactory}. + * + * @author Andy Wilkinson + */ +@Configuration(proxyBeanMethods = false) +@ConditionalOnClass(ConnectionFactory.class) +@ConditionalOnSingleCandidate(ConnectionFactory.class) +class R2dbcInitializationConfiguration { + + @Bean + R2dbcScriptDatabaseInitializer r2dbcScriptDatabaseInitializer(ConnectionFactory connectionFactory, + SqlInitializationProperties properties) { + DatabaseInitializationSettings settings = SettingsCreator.createFrom(properties); + return new R2dbcScriptDatabaseInitializer( + determineConnectionFactory(connectionFactory, properties.getUsername(), properties.getPassword()), + settings); + } + + private static ConnectionFactory determineConnectionFactory(ConnectionFactory connectionFactory, String username, + String password) { + if (StringUtils.hasText(username) && StringUtils.hasText(password)) { + ConnectionFactoryBuilder.derivefrom(connectionFactory).username(username).password(password).build(); + } + return connectionFactory; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java new file mode 100644 index 0000000000..cec633db6d --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.sql.init; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.boot.sql.init.DatabaseInitializationSettings; + +/** + * Helpers class for creating {@link DatabaseInitializationSettings} from + * {@link SqlInitializationProperties}. + * + * @author Andy Wilkinson + */ +final class SettingsCreator { + + private SettingsCreator() { + + } + + static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) { + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); + settings.setSchemaLocations( + scriptLocations(properties.getSchemaLocations(), "schema", properties.getPlatform())); + settings.setDataLocations(scriptLocations(properties.getDataLocations(), "data", properties.getPlatform())); + settings.setContinueOnError(properties.isContinueOnError()); + settings.setSeparator(properties.getSeparator()); + settings.setEncoding(properties.getEncoding()); + return settings; + } + + private static List scriptLocations(List locations, String fallback, String platform) { + if (locations != null) { + return locations; + } + List fallbackLocations = new ArrayList<>(); + fallbackLocations.add("optional:classpath*:" + fallback + "-" + platform + ".sql"); + fallbackLocations.add("optional:classpath*:" + fallback + ".sql"); + return fallbackLocations; + } + +} diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java index 87b864e781..33d61fa9f6 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java @@ -16,27 +16,16 @@ package org.springframework.boot.autoconfigure.sql.init; -import java.util.ArrayList; -import java.util.List; - -import javax.sql.DataSource; - import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.boot.jdbc.init.DataSourceInitializationSettings; -import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer; -import org.springframework.boot.jdbc.init.dependency.DataSourceInitializationDependencyConfigurer; -import org.springframework.context.annotation.Bean; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.jdbc.datasource.SimpleDriverDataSource; -import org.springframework.util.StringUtils; /** * {@link EnableAutoConfiguration Auto-configuration} for initializing an SQL database. @@ -45,49 +34,11 @@ import org.springframework.util.StringUtils; * @since 2.5.0 */ @Configuration(proxyBeanMethods = false) -@ConditionalOnMissingBean(ScriptDataSourceInitializer.class) -@ConditionalOnSingleCandidate(DataSource.class) +@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class) @ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true) -@AutoConfigureAfter(DataSourceAutoConfiguration.class) +@AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class }) @EnableConfigurationProperties(SqlInitializationProperties.class) -@Import(DataSourceInitializationDependencyConfigurer.class) +@Import({ R2dbcInitializationConfiguration.class, DataSourceInitializationConfiguration.class }) public class SqlInitializationAutoConfiguration { - @Bean - ScriptDataSourceInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource, - SqlInitializationProperties initializationProperties) { - DataSourceInitializationSettings settings = createSettings(initializationProperties); - return new ScriptDataSourceInitializer(determineDataSource(dataSource, initializationProperties.getUsername(), - initializationProperties.getPassword()), settings); - } - - private static DataSourceInitializationSettings createSettings(SqlInitializationProperties properties) { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); - settings.setSchemaLocations( - scriptLocations(properties.getSchemaLocations(), "schema", properties.getPlatform())); - settings.setDataLocations(scriptLocations(properties.getDataLocations(), "data", properties.getPlatform())); - settings.setContinueOnError(properties.isContinueOnError()); - settings.setSeparator(properties.getSeparator()); - settings.setEncoding(properties.getEncoding()); - return settings; - } - - private static List scriptLocations(List locations, String fallback, String platform) { - if (locations != null) { - return locations; - } - List fallbackLocations = new ArrayList<>(); - fallbackLocations.add("optional:classpath*:" + fallback + "-" + platform + ".sql"); - fallbackLocations.add("optional:classpath*:" + fallback + ".sql"); - return fallbackLocations; - } - - private static DataSource determineDataSource(DataSource dataSource, String username, String password) { - if (StringUtils.hasText(username) && StringUtils.hasText(password)) { - DataSourceBuilder.derivedFrom(dataSource).username(username).password(password) - .type(SimpleDriverDataSource.class).build(); - } - return dataSource; - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java index 8010165e47..7a94d57d2d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfigurationTests.java @@ -43,7 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; -import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer; +import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.jdbc.init.dependency.DependsOnDataSourceInitialization; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.assertj.AssertableApplicationContext; @@ -240,14 +240,15 @@ class DataSourceAutoConfigurationTests { void testDataSourceIsInitializedEarly() { this.contextRunner.withUserConfiguration(TestInitializedDataSourceConfiguration.class) .withPropertyValues("spring.datasource.initialization-mode=always").run((context) -> { - assertThat(context).hasSingleBean(ScriptDataSourceInitializer.class); + assertThat(context).hasSingleBean(DataSourceScriptDatabaseInitializer.class); assertThat(context.getBean(TestInitializedDataSourceConfiguration.class).called).isTrue(); }); } @Test void whenNoInitializationRelatedSpringDataSourcePropertiesAreConfiguredThenInitializationBacksOff() { - this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ScriptDataSourceInitializer.class)); + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(DataSourceScriptDatabaseInitializer.class)); } private static Function hideConnectionPools() { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java new file mode 100644 index 0000000000..224c850590 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java @@ -0,0 +1,119 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.sql.init; + +import java.nio.charset.Charset; +import java.util.List; + +import javax.sql.DataSource; + +import io.r2dbc.spi.ConnectionFactory; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; +import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; +import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link SqlInitializationAutoConfiguration}. + * + * @author Andy Wilkinson + */ +public class SqlInitializationAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SqlInitializationAutoConfiguration.class)).withPropertyValues( + "spring.datasource.generate-unique-name:true", "spring.r2dbc.generate-unique-name:true"); + + @Test + void whenNoDataSourceOrConnectionFactoryIsAvailableThenAutoConfigurationBacksOff() { + this.contextRunner + .run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); + } + + @Test + void whenConnectionFactoryIsAvailableThenR2dbcInitializerIsAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .run((context) -> assertThat(context).hasSingleBean(R2dbcScriptDatabaseInitializer.class)); + } + + @Test + void whenConnectionFactoryIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .withPropertyValues("spring.sql.init.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); + } + + @Test + void whenDataSourceIsAvailableThenDataSourceInitializerIsAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .run((context) -> assertThat(context).hasSingleBean(DataSourceScriptDatabaseInitializer.class)); + } + + @Test + void whenDataSourceIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) + .withPropertyValues("spring.sql.init.enabled:false") + .run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); + } + + @Test + void whenDataSourceAndConnectionFactoryAreAvailableThenOnlyR2dbcInitializerIsAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .withUserConfiguration(DataSourceAutoConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(ConnectionFactory.class) + .hasSingleBean(DataSource.class).hasSingleBean(R2dbcScriptDatabaseInitializer.class) + .doesNotHaveBean(DataSourceScriptDatabaseInitializer.class)); + } + + @Test + void whenAnInitializerIsDefinedThenInitializerIsNotAutoConfigured() { + this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) + .withUserConfiguration(DataSourceAutoConfiguration.class, DatabaseInitializerConfiguration.class) + .run((context) -> assertThat(context).hasSingleBean(AbstractScriptDatabaseInitializer.class) + .hasBean("customInitializer")); + } + + @Configuration(proxyBeanMethods = false) + static class DatabaseInitializerConfiguration { + + @Bean + AbstractScriptDatabaseInitializer customInitializer() { + return new AbstractScriptDatabaseInitializer(new DatabaseInitializationSettings()) { + + @Override + protected void runScripts(List resources, boolean continueOnError, String separator, + Charset encoding) { + // No-op + } + + }; + } + + } + +} diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc index fb1c70bfaa..0761ecc90d 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto.adoc @@ -1573,7 +1573,6 @@ The following example shows how to define a data source by setting properties: ---- Assuming that your `FancyDataSource` has regular JavaBean properties for the URL, the username, and the pool size, these settings are bound automatically before the `DataSource` is made available to other components. -The regular <> also happens (so the relevant sub-set of `spring.datasource.*` can still be used with your custom configuration). Spring Boot also provides a utility builder class, called `DataSourceBuilder`, that can be used to create one of the standard data sources (if it is on the classpath). The builder can detect the one to use based on what's available on the classpath. @@ -2022,55 +2021,26 @@ It is a Hibernate feature (and has nothing to do with Spring). -[[howto-initialize-a-database-using-spring-jdbc]] +[[howto-initialize-a-database-using-basic-scripts]] === Initialize a Database using basic SQL scripts -Spring Boot can automatically create the schema (DDL scripts) of your `DataSource` and initialize it (DML scripts). +Spring Boot can automatically create the schema (DDL scripts) of your JDBC `DataSource` or R2DBC `ConnectionFactory` and initialize it (DML scripts). It loads SQL from the standard root classpath locations: `schema.sql` and `data.sql`, respectively. In addition, Spring Boot processes the `schema-$\{platform}.sql` and `data-$\{platform}.sql` files (if present), where `platform` is the value of configprop:spring.sql.init.platform[]. This allows you to switch to database-specific scripts if necessary. For example, you might choose to set it to the vendor name of the database (`hsqldb`, `h2`, `oracle`, `mysql`, `postgresql`, and so on). +SQL database initialization can be disabled by setting configprop:spring.sql.init.enabled[] to `false`. +By default, Spring Boot enables the fail-fast feature of its script-based database initializer. +This means that, if the scripts cause exceptions, the application fails to start. +You can tune that behavior by setting configprop:spring.sql.init.continue-on-error[]. -[NOTE] -==== -When only basic SQL scripts are used, Spring Boot automatically initializes the `DataSource`. -This initialization can be disabled by setting the configprop:spring.sql.init.enabled[] property to `false`. - -By default, script-based `DataSource` initialization is performed before any JPA `EntityManagerFactory` beans are created. +Script-based `DataSource` initialization is performed, by default, before any JPA `EntityManagerFactory` beans are created. `schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it. -We do not recommend using multiple data source initialization technologies. -However, if you want script-based `DataSource` initialization to be able to build upon the schema creation performed by Hibernate, set configprop:spring.jpa.defer-datasource-initialization[] to `true`. +While do not recommend using multiple data source initialization technologies, if you want script-based `DataSource` initialization to be able to build upon the schema creation performed by Hibernate, set configprop:spring.jpa.defer-datasource-initialization[] to `true`. This will defer data source initialization until after any `EntityManagerFactory` beans have been created and initialized. `schema.sql` can then be used to make additions to any schema creation performed by Hibernate and `data.sql` can be used to populate it. If you are using a <>, like Flyway or Liquibase, you should use them alone to create and initialize the schema. Using the basic `schema.sql` and `data.sql` scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release. -==== - -By default, Spring Boot enables the fail-fast feature of the Spring JDBC initializer. -This means that, if the scripts cause exceptions, the application fails to start. -You can tune that behavior by setting configprop:spring.sql.init.continue-on-error[]. - -To take complete control over the script-based initialization of a `DataSource`, define your own `ScriptDataSourceInitializer` bean. -Doing so will cause the auto-configuration of script-based initialization to back off. -If you have multiple `DataSource`s in your application, you can define multiple `ScriptDataSourceInitializer` beans. - - - -[[howto-initialize-a-database-using-r2dbc]] -=== Initialize a Database Using R2DBC -If you are using R2DBC, the regular `DataSource` auto-configuration backs off so none of the options described above can be used. - -You can initialize the database on startup using SQL scripts as shown in the following example: - -[source,java,indent=0] ----- -include::{include-howto}/dataaccess/R2dbcDatabaseInitializationConfiguration.java[tag=*] ----- - -Alternatively, you can configure either <> or <> to configure a `DataSource` for you for the duration of the migration. -Both these libraries offer properties to set the `url`, `username` and `password` of the database to migrate. - -NOTE: When choosing this option, `org.springframework:spring-jdbc` is still a required dependency. diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 8ac23d9cf2..a3af157826 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -3936,7 +3936,7 @@ TIP: You do not need to specify a driver class name, since Spring Boot obtains t NOTE: At least the url should be provided. Information specified in the URL takes precedence over individual properties, i.e. `name`, `username`, `password` and pooling options. -TIP: The "`How-to`" section includes a <>. +TIP: The "`How-to`" section includes a <>. To customize the connections created by a `ConnectionFactory`, i.e., set specific parameters that you do not want (or cannot) configure in your central database configuration, you can use a `ConnectionFactoryOptionsBuilderCustomizer` `@Bean`. The following example shows how to manually override the database port while the rest of the options is taken from the application configuration: diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java new file mode 100644 index 0000000000..d453c7a4be --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.jdbc.init; + +import java.nio.charset.Charset; +import java.util.List; + +import javax.sql.DataSource; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; +import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; + +/** + * {@link InitializingBean} that performs {@link DataSource} initialization using schema + * (DDL) and data (DML) scripts. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer { + + private final DataSource dataSource; + + /** + * Creates a new {@link DataSourceScriptDatabaseInitializer} that will initialize the + * given {@code DataSource} using the given settings. + * @param dataSource data source to initialize + * @param settings initialization settings + */ + public DataSourceScriptDatabaseInitializer(DataSource dataSource, DatabaseInitializationSettings settings) { + super(settings); + this.dataSource = dataSource; + } + + /** + * Returns the {@code DataSource} that will be initialized. + * @return the initialization data source + */ + protected final DataSource getDataSource() { + return this.dataSource; + } + + @Override + protected void runScripts(List resources, boolean continueOnError, String separator, Charset encoding) { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.setContinueOnError(continueOnError); + populator.setSeparator(separator); + if (encoding != null) { + populator.setSqlScriptEncoding(encoding.name()); + } + for (Resource resource : resources) { + populator.addScript(resource); + } + DatabasePopulatorUtils.execute(populator, this.dataSource); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerDetector.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerDetector.java similarity index 78% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerDetector.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerDetector.java index 254d62b25d..1cbcb840a5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerDetector.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerDetector.java @@ -23,15 +23,16 @@ import org.springframework.boot.jdbc.init.dependency.AbstractBeansOfTypeDataSour import org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector; /** - * A {@link DataSourceInitializerDetector} for {@link ScriptDataSourceInitializer}. + * A {@link DataSourceInitializerDetector} for + * {@link DataSourceScriptDatabaseInitializer}. * * @author Andy Wilkinson */ -class ScriptDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { +class DataSourceScriptDatabaseInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { @Override protected Set> getDataSourceInitializerBeanTypes() { - return Collections.singleton(ScriptDataSourceInitializer.class); + return Collections.singleton(DataSourceScriptDatabaseInitializer.class); } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java index 4495a2b635..156ea334f2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/package-info.java @@ -15,6 +15,7 @@ */ /** - * Support for initializaton of a JDBC {@code DataSource}. + * Support for initializaton of an SQL database using a JDBC {@link javax.sql.DataSource + * DataSource}. */ package org.springframework.boot.jdbc.init; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java new file mode 100644 index 0000000000..fedfea6eea --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.r2dbc.init; + +import java.nio.charset.Charset; +import java.util.List; + +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.core.io.Resource; +import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; + +/** + * An {@link InitializingBean} that initializes a database represented by an R2DBC + * {@link ConnectionFactory}. + * + * @author Andy Wilkinson + * @since 2.5.0 + */ +public class R2dbcScriptDatabaseInitializer extends AbstractScriptDatabaseInitializer { + + private final ConnectionFactory connectionFactory; + + /** + * Creates a new {@code R2dbcScriptDatabaseInitializer} that will initialize the + * database recognized by the given {@code connectionFactory} using the given + * {@code settings}. + * @param connectionFactory connectionFactory for the database + * @param settings initialization settings + */ + public R2dbcScriptDatabaseInitializer(ConnectionFactory connectionFactory, + DatabaseInitializationSettings settings) { + super(settings); + this.connectionFactory = connectionFactory; + } + + @Override + protected void runScripts(List scripts, boolean continueOnError, String separator, Charset encoding) { + ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); + populator.setContinueOnError(continueOnError); + populator.setSeparator(separator); + if (encoding != null) { + populator.setSqlScriptEncoding(encoding.name()); + } + for (Resource script : scripts) { + populator.addScript(script); + } + populator.populate(this.connectionFactory).block(); + } + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/package-info.java new file mode 100644 index 0000000000..adb8e093f7 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-2021 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 + * + * https://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. + */ + +/** + * Support for initializaton of an SQL database using an R2DBC + * {@link io.r2dbc.spi.ConnectionFactory ConnectionFactory}. + */ +package org.springframework.boot.r2dbc.init; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java similarity index 75% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializer.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java index 105133b401..cdb3630688 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.jdbc.init; +package org.springframework.boot.sql.init; import java.io.IOException; import java.nio.charset.Charset; @@ -23,54 +23,38 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import javax.sql.DataSource; - import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; -import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; -import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.util.CollectionUtils; /** - * {@link InitializingBean} that performs {@link DataSource} initialization using schema - * (DDL) and data (DML) scripts. + * Base class for an {@link InitializingBean} that performs SQL database initialization + * using schema (DDL) and data (DML) scripts. * * @author Andy Wilkinson * @since 2.5.0 */ -public class ScriptDataSourceInitializer implements ResourceLoaderAware, InitializingBean { +public abstract class AbstractScriptDatabaseInitializer implements ResourceLoaderAware, InitializingBean { private static final String OPTIONAL_LOCATION_PREFIX = "optional:"; - private final DataSource dataSource; - - private final DataSourceInitializationSettings settings; + private final DatabaseInitializationSettings settings; private volatile ResourceLoader resourceLoader; /** - * Creates a new {@link ScriptDataSourceInitializer} that will initialize the given - * {@code DataSource} using the given settings. - * @param dataSource data source to initialize + * Creates a new {@link AbstractScriptDatabaseInitializer} that will initialize the + * database using the given settings. * @param settings initialization settings */ - public ScriptDataSourceInitializer(DataSource dataSource, DataSourceInitializationSettings settings) { - this.dataSource = dataSource; + protected AbstractScriptDatabaseInitializer(DatabaseInitializationSettings settings) { this.settings = settings; } - /** - * Returns the {@code DataSource} that will be initialized. - * @return the initialization data source - */ - protected final DataSource getDataSource() { - return this.dataSource; - } - @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; @@ -148,18 +132,8 @@ public class ScriptDataSourceInitializer implements ResourceLoaderAware, Initial this.settings.getEncoding()); } - protected void runScripts(List resources, boolean continueOnError, String separator, Charset encoding) { - ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); - populator.setContinueOnError(continueOnError); - populator.setSeparator(separator); - if (encoding != null) { - populator.setSqlScriptEncoding(encoding.name()); - } - for (Resource resource : resources) { - populator.addScript(resource); - } - DatabasePopulatorUtils.execute(populator, this.dataSource); - } + protected abstract void runScripts(List resources, boolean continueOnError, String separator, + Charset encoding); private static class ScriptLocationResolver { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationSettings.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java similarity index 94% rename from spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationSettings.java rename to spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java index 64e4df3b72..62193407c2 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceInitializationSettings.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java @@ -14,20 +14,18 @@ * limitations under the License. */ -package org.springframework.boot.jdbc.init; +package org.springframework.boot.sql.init; import java.nio.charset.Charset; import java.util.List; -import javax.sql.DataSource; - /** - * Settings for initializing a database using a JDBC {@link DataSource}. + * Settings for initializing an SQL database. * * @author Andy Wilkinson * @since 2.5.0 */ -public class DataSourceInitializationSettings { +public class DatabaseInitializationSettings { private List schemaLocations; diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/package-info.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/package-info.java new file mode 100644 index 0000000000..a325f9d1b3 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2021 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 + * + * https://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. + */ + +/** + * Support for initializaton of an SQL database. + */ +package org.springframework.boot.sql.init; diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories index d0812eefc9..2c562ded8d 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/spring.factories @@ -82,7 +82,7 @@ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter # DataSource Initializer Detectors org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector=\ org.springframework.boot.flyway.FlywayDataSourceInitializerDetector,\ -org.springframework.boot.jdbc.init.ScriptDataSourceInitializerDetector,\ +org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializerDetector,\ org.springframework.boot.liquibase.LiquibaseDataSourceInitializerDetector,\ org.springframework.boot.orm.jpa.JpaDataSourceInitializerDetector diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java new file mode 100644 index 0000000000..708c567565 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.jdbc.init; + +import java.util.UUID; + +import com.zaxxer.hikari.HikariDataSource; +import org.junit.jupiter.api.AfterEach; + +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.jdbc.core.JdbcTemplate; + +/** + * Tests for {@link DataSourceScriptDatabaseInitializer}. + * + * @author Andy Wilkinson + */ +class DataSourceScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests { + + private final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class) + .url("jdbc:h2:mem:" + UUID.randomUUID()).build(); + + @AfterEach + void closeDataSource() { + this.dataSource.close(); + } + + @Override + protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { + return new DataSourceScriptDatabaseInitializer(this.dataSource, settings); + } + + @Override + protected int numberOfRows(String sql) { + return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java new file mode 100644 index 0000000000..2bc81ff5b2 --- /dev/null +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2021 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 + * + * https://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.r2dbc.init; + +import java.util.UUID; + +import io.r2dbc.spi.ConnectionFactory; + +import org.springframework.boot.r2dbc.ConnectionFactoryBuilder; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; +import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests; +import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.r2dbc.core.DatabaseClient; + +/** + * Tests for {@link R2dbcScriptDatabaseInitializer}. + * + * @author Andy Wilkinson + */ +class R2dbcScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests { + + private final ConnectionFactory connectionFactory = ConnectionFactoryBuilder + .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") + .build(); + + @Override + protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { + return new R2dbcScriptDatabaseInitializer(this.connectionFactory, settings); + } + + @Override + protected int numberOfRows(String sql) { + return DatabaseClient.create(this.connectionFactory).sql(sql).map((row, metadata) -> row.get(0)).first() + .map((number) -> ((Number) number).intValue()).block(); + } + +} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java similarity index 59% rename from spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerTests.java rename to spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java index f235b85abe..cb1e87d5dc 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/ScriptDataSourceInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java @@ -14,105 +14,88 @@ * limitations under the License. */ -package org.springframework.boot.jdbc.init; +package org.springframework.boot.sql.init; import java.util.Arrays; -import java.util.UUID; -import com.zaxxer.hikari.HikariDataSource; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.JdbcTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; /** - * Tests for {@link ScriptDataSourceInitializer}. + * Base class for testing {@link AbstractScriptDatabaseInitializer} implementations. * * @author Andy Wilkinson */ -class ScriptDataSourceInitializerTests { - - private final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class) - .url("jdbc:h2:mem:" + UUID.randomUUID()).build(); - - @AfterEach - void closeDataSource() { - this.dataSource.close(); - } +public abstract class AbstractScriptDatabaseInitializerTests { @Test void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("schema.sql")); settings.setDataLocations(Arrays.asList("data.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThat(initializer.initializeDatabase()).isTrue(); assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1); } @Test void whenContinueOnErrorIsFalseThenInitializationFailsOnError() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("data.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase()); } @Test void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setContinueOnError(true); settings.setDataLocations(Arrays.asList("data.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThat(initializer.initializeDatabase()).isTrue(); } @Test void whenNoScriptsExistAtASchemaLocationThenInitializationFails() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("does-not-exist.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) .withMessage("No schema scripts found at location 'does-not-exist.sql'"); } @Test void whenNoScriptsExistAtADataLocationThenInitializationFails() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("does-not-exist.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) .withMessage("No data scripts found at location 'does-not-exist.sql'"); } @Test void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThat(initializer.initializeDatabase()).isFalse(); } @Test void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() { - DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); + DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql")); - ScriptDataSourceInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createInitializer(settings); assertThat(initializer.initializeDatabase()).isFalse(); } - private ScriptDataSourceInitializer createInitializer(DataSourceInitializationSettings settings) { - return new ScriptDataSourceInitializer(this.dataSource, settings); - } + protected abstract AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings); - private int numberOfRows(String sql) { - return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class); - } + protected abstract int numberOfRows(String sql); } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/java/smoketest/data/r2dbc/SampleR2dbcApplication.java b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/java/smoketest/data/r2dbc/SampleR2dbcApplication.java index 2b75343e8e..9fb75db79a 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/java/smoketest/data/r2dbc/SampleR2dbcApplication.java +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/java/smoketest/data/r2dbc/SampleR2dbcApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,15 +16,8 @@ package smoketest.data.r2dbc; -import io.r2dbc.spi.ConnectionFactory; - -import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; @SpringBootApplication public class SampleR2dbcApplication { @@ -33,13 +26,4 @@ public class SampleR2dbcApplication { SpringApplication.run(SampleR2dbcApplication.class, args); } - @Bean - public ApplicationRunner initializeDatabase(ConnectionFactory connectionFactory, ResourceLoader resourceLoader) { - return (arguments) -> { - Resource[] scripts = new Resource[] { resourceLoader.getResource("classpath:database-init.sql") }; - new ResourceDatabasePopulator(scripts).populate(connectionFactory).block(); - }; - - } - } diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/database-init.sql b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/data.sql similarity index 56% rename from spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/database-init.sql rename to spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/data.sql index 9b61dc66b2..b08c2abbb9 100644 --- a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/database-init.sql +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/data.sql @@ -1,10 +1,2 @@ -CREATE TABLE CITY ( - id INTEGER IDENTITY PRIMARY KEY, - name VARCHAR(30), - state VARCHAR(30), - country VARCHAR(30) -); - INSERT INTO CITY (ID, NAME, STATE, COUNTRY) values (2000, 'Washington', 'DC', 'US'); INSERT INTO CITY (ID, NAME, STATE, COUNTRY) values (2001, 'San Francisco', 'CA', 'US'); - diff --git a/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/schema.sql b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/schema.sql new file mode 100644 index 0000000000..88217dc41b --- /dev/null +++ b/spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE CITY ( + id INTEGER IDENTITY PRIMARY KEY, + name VARCHAR(30), + state VARCHAR(30), + country VARCHAR(30) +);