From e49a88cb9b680248e3aa1876ffa69be3fc029436 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 26 Mar 2021 17:31:44 +0000 Subject: [PATCH] Auto-configure script-based R2DBC database initialization See gh-24741 --- ...DataSourceInitializationConfiguration.java | 56 +++++++++ .../R2dbcInitializationConfiguration.java | 58 +++++++++ .../sql/init/SettingsCreator.java | 57 +++++++++ .../SqlInitializationAutoConfiguration.java | 59 +-------- ...lInitializationAutoConfigurationTests.java | 119 ++++++++++++++++++ .../src/docs/asciidoc/howto.adoc | 46 ++----- .../docs/asciidoc/spring-boot-features.adoc | 2 +- .../data/r2dbc/SampleR2dbcApplication.java | 18 +-- .../resources/{database-init.sql => data.sql} | 8 -- .../src/main/resources/schema.sql | 6 + 10 files changed, 311 insertions(+), 118 deletions(-) create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/R2dbcInitializationConfiguration.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java rename spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/{database-init.sql => data.sql} (56%) create mode 100644 spring-boot-tests/spring-boot-smoke-tests/spring-boot-smoke-test-data-r2dbc/src/main/resources/schema.sql 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 00000000000..6b5cfbc02e6 --- /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 00000000000..e6aee342edf --- /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 00000000000..cec633db6d7 --- /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 c723d29c676..33d61fa9f66 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.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.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(DataSourceScriptDatabaseInitializer.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 - DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource, - SqlInitializationProperties initializationProperties) { - DatabaseInitializationSettings settings = createSettings(initializationProperties); - return new DataSourceScriptDatabaseInitializer(determineDataSource(dataSource, - initializationProperties.getUsername(), initializationProperties.getPassword()), settings); - } - - private static DatabaseInitializationSettings createSettings(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; - } - - 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/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 00000000000..224c8505908 --- /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 fb1c70bfaa4..0761ecc90d8 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 8ac23d9cf2f..a3af1578264 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-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 2b75343e8ed..9fb75db79a6 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 9b61dc66b2e..b08c2abbb99 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 00000000000..88217dc41bb --- /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) +);