Merge branch 'r2dbc-init'

Closes gh-24741
pull/25829/head
Andy Wilkinson 4 years ago
commit ee41dbdd8f

@ -32,8 +32,8 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.init.DataSourceInitializationSettings; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -148,9 +148,10 @@ class LiquibaseEndpointTests {
DataSource dataSource = new EmbeddedDatabaseBuilder() DataSource dataSource = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseConnection.get(getClass().getClassLoader()).getType()) .setType(EmbeddedDatabaseConnection.get(getClass().getClassLoader()).getType())
.setName(UUID.randomUUID().toString()).build(); .setName(UUID.randomUUID().toString()).build();
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("classpath:/db/create-custom-schema.sql")); settings.setSchemaLocations(Arrays.asList("classpath:/db/create-custom-schema.sql"));
ScriptDataSourceInitializer initializer = new ScriptDataSourceInitializer(dataSource, settings); DataSourceScriptDatabaseInitializer initializer = new DataSourceScriptDatabaseInitializer(dataSource,
settings);
initializer.initializeDatabase(); initializer.initializeDatabase();
return dataSource; return dataSource;
} }

@ -41,9 +41,9 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfi
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DataSourceInitializationMode; import org.springframework.boot.jdbc.DataSourceInitializationMode;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.init.DataSourceInitializationSettings; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.jdbc.init.ScriptDataSourceInitializer;
import org.springframework.boot.jdbc.init.dependency.DataSourceInitializationDependencyConfigurer; 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.Bean;
import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.DependsOn;
@ -56,7 +56,7 @@ import org.springframework.util.StringUtils;
/** /**
* Configuration for {@link DataSource} initialization using a * Configuration for {@link DataSource} initialization using a
* {@link ScriptDataSourceInitializer} with DDL and DML scripts. * {@link DataSourceScriptDatabaseInitializer} with DDL and DML scripts.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
@ -87,13 +87,13 @@ class DataSourceInitializationConfiguration {
@org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class) @org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class)
@org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class) @org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class)
@ConditionalOnSingleCandidate(DataSource.class) @ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnMissingBean(ScriptDataSourceInitializer.class) @ConditionalOnMissingBean(DataSourceScriptDatabaseInitializer.class)
static class InitializationSpecificCredentialsDataSourceInitializationConfiguration { static class InitializationSpecificCredentialsDataSourceInitializationConfiguration {
@Bean @Bean
ScriptDataSourceInitializer ddlOnlyScriptDataSourceInitializer(ObjectProvider<DataSource> dataSource, DataSourceScriptDatabaseInitializer ddlOnlyScriptDataSourceInitializer(ObjectProvider<DataSource> dataSource,
DataSourceProperties properties, ResourceLoader resourceLoader) { DataSourceProperties properties, ResourceLoader resourceLoader) {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform())); settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform()));
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
@ -106,9 +106,9 @@ class DataSourceInitializationConfiguration {
@Bean @Bean
@DependsOn("ddlOnlyScriptDataSourceInitializer") @DependsOn("ddlOnlyScriptDataSourceInitializer")
ScriptDataSourceInitializer dmlOnlyScriptDataSourceInitializer(ObjectProvider<DataSource> dataSource, DataSourceScriptDatabaseInitializer dmlOnlyScriptDataSourceInitializer(ObjectProvider<DataSource> dataSource,
DataSourceProperties properties, ResourceLoader resourceLoader) { DataSourceProperties properties, ResourceLoader resourceLoader) {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform())); settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform()));
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
@ -144,13 +144,13 @@ class DataSourceInitializationConfiguration {
@org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class) @org.springframework.context.annotation.Import(DataSourceInitializationDependencyConfigurer.class)
@org.springframework.context.annotation.Conditional(DataSourceInitializationCondition.class) @org.springframework.context.annotation.Conditional(DataSourceInitializationCondition.class)
@ConditionalOnSingleCandidate(DataSource.class) @ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnMissingBean(ScriptDataSourceInitializer.class) @ConditionalOnMissingBean(DataSourceScriptDatabaseInitializer.class)
static class SharedCredentialsDataSourceInitializationConfiguration { static class SharedCredentialsDataSourceInitializationConfiguration {
@Bean @Bean
ScriptDataSourceInitializer scriptDataSourceInitializer(DataSource dataSource, DataSourceProperties properties, DataSourceScriptDatabaseInitializer scriptDataSourceInitializer(DataSource dataSource,
ResourceLoader resourceLoader) { DataSourceProperties properties, ResourceLoader resourceLoader) {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform())); settings.setSchemaLocations(scriptLocations(properties.getSchema(), "schema", properties.getPlatform()));
settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform())); settings.setDataLocations(scriptLocations(properties.getData(), "data", properties.getPlatform()));
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
@ -188,12 +188,12 @@ class DataSourceInitializationConfiguration {
} }
static class InitializationModeDataSourceScriptDatabaseInitializer extends ScriptDataSourceInitializer { static class InitializationModeDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {
private final DataSourceInitializationMode mode; private final DataSourceInitializationMode mode;
InitializationModeDataSourceScriptDatabaseInitializer(DataSource dataSource, InitializationModeDataSourceScriptDatabaseInitializer(DataSource dataSource,
DataSourceInitializationSettings settings, DataSourceInitializationMode mode) { DatabaseInitializationSettings settings, DataSourceInitializationMode mode) {
super(dataSource, settings); super(dataSource, settings);
this.mode = mode; this.mode = mode;
} }

@ -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;
}
}

@ -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;
}
}

@ -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<String> scriptLocations(List<String> locations, String fallback, String platform) {
if (locations != null) {
return locations;
}
List<String> fallbackLocations = new ArrayList<>();
fallbackLocations.add("optional:classpath*:" + fallback + "-" + platform + ".sql");
fallbackLocations.add("optional:classpath*:" + fallback + ".sql");
return fallbackLocations;
}
}

@ -16,27 +16,16 @@
package org.springframework.boot.autoconfigure.sql.init; 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.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 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.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
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.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; 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. * {@link EnableAutoConfiguration Auto-configuration} for initializing an SQL database.
@ -45,49 +34,11 @@ import org.springframework.util.StringUtils;
* @since 2.5.0 * @since 2.5.0
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(ScriptDataSourceInitializer.class) @ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnSingleCandidate(DataSource.class)
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true) @ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class })
@EnableConfigurationProperties(SqlInitializationProperties.class) @EnableConfigurationProperties(SqlInitializationProperties.class)
@Import(DataSourceInitializationDependencyConfigurer.class) @Import({ R2dbcInitializationConfiguration.class, DataSourceInitializationConfiguration.class })
public class SqlInitializationAutoConfiguration { 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<String> scriptLocations(List<String> locations, String fallback, String platform) {
if (locations != null) {
return locations;
}
List<String> 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;
}
} }

@ -43,7 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DatabaseDriver; import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; 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.jdbc.init.dependency.DependsOnDataSourceInitialization;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
@ -240,14 +240,15 @@ class DataSourceAutoConfigurationTests {
void testDataSourceIsInitializedEarly() { void testDataSourceIsInitializedEarly() {
this.contextRunner.withUserConfiguration(TestInitializedDataSourceConfiguration.class) this.contextRunner.withUserConfiguration(TestInitializedDataSourceConfiguration.class)
.withPropertyValues("spring.datasource.initialization-mode=always").run((context) -> { .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(); assertThat(context.getBean(TestInitializedDataSourceConfiguration.class).called).isTrue();
}); });
} }
@Test @Test
void whenNoInitializationRelatedSpringDataSourcePropertiesAreConfiguredThenInitializationBacksOff() { void whenNoInitializationRelatedSpringDataSourcePropertiesAreConfiguredThenInitializationBacksOff() {
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(ScriptDataSourceInitializer.class)); this.contextRunner
.run((context) -> assertThat(context).doesNotHaveBean(DataSourceScriptDatabaseInitializer.class));
} }
private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() { private static Function<ApplicationContextRunner, ApplicationContextRunner> hideConnectionPools() {

@ -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<Resource> resources, boolean continueOnError, String separator,
Charset encoding) {
// No-op
}
};
}
}
}

@ -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. 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 <<howto-initialize-a-database-using-spring-jdbc,database initialization>> 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). 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. 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 === 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. 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[]. 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. 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). 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] Script-based `DataSource` initialization is performed, by default, before any JPA `EntityManagerFactory` beans are created.
====
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.
`schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it. `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. 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`.
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`.
This will defer data source initialization until after any `EntityManagerFactory` beans have been created and initialized. 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. `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 <<spring-boot-features.adoc#howto-use-a-higher-level-database-migration-tool,Higher-level Database Migration Tool>>, like Flyway or Liquibase, you should use them alone to create and initialize the schema. If you are using a <<spring-boot-features.adoc#howto-use-a-higher-level-database-migration-tool,Higher-level Database Migration Tool>>, 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. 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 <<howto-execute-flyway-database-migrations-on-startup,Flyway>> or <<howto-execute-liquibase-database-migrations-on-startup,Liquibase>> 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.

@ -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. 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. 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 <<howto.adoc#howto-initialize-a-database-using-r2dbc, section on how to initialize a database>>. TIP: The "`How-to`" section includes a <<howto.adoc#howto-initialize-a-database-using-basic-scripts, section on how to initialize a database>>.
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`. 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: The following example shows how to manually override the database port while the rest of the options is taken from the application configuration:

@ -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<Resource> 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);
}
}

@ -23,15 +23,16 @@ import org.springframework.boot.jdbc.init.dependency.AbstractBeansOfTypeDataSour
import org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector; import org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector;
/** /**
* A {@link DataSourceInitializerDetector} for {@link ScriptDataSourceInitializer}. * A {@link DataSourceInitializerDetector} for
* {@link DataSourceScriptDatabaseInitializer}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
class ScriptDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector { class DataSourceScriptDatabaseInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
@Override @Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() { protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
return Collections.singleton(ScriptDataSourceInitializer.class); return Collections.singleton(DataSourceScriptDatabaseInitializer.class);
} }
} }

@ -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; package org.springframework.boot.jdbc.init;

@ -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<Resource> 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();
}
}

@ -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;

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.jdbc.init; package org.springframework.boot.sql.init;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -23,54 +23,38 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware; import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils; 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; import org.springframework.util.CollectionUtils;
/** /**
* {@link InitializingBean} that performs {@link DataSource} initialization using schema * Base class for an {@link InitializingBean} that performs SQL database initialization
* (DDL) and data (DML) scripts. * using schema (DDL) and data (DML) scripts.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @since 2.5.0 * @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 static final String OPTIONAL_LOCATION_PREFIX = "optional:";
private final DataSource dataSource; private final DatabaseInitializationSettings settings;
private final DataSourceInitializationSettings settings;
private volatile ResourceLoader resourceLoader; private volatile ResourceLoader resourceLoader;
/** /**
* Creates a new {@link ScriptDataSourceInitializer} that will initialize the given * Creates a new {@link AbstractScriptDatabaseInitializer} that will initialize the
* {@code DataSource} using the given settings. * database using the given settings.
* @param dataSource data source to initialize
* @param settings initialization settings * @param settings initialization settings
*/ */
public ScriptDataSourceInitializer(DataSource dataSource, DataSourceInitializationSettings settings) { protected AbstractScriptDatabaseInitializer(DatabaseInitializationSettings settings) {
this.dataSource = dataSource;
this.settings = 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 @Override
public void setResourceLoader(ResourceLoader resourceLoader) { public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader; this.resourceLoader = resourceLoader;
@ -148,18 +132,8 @@ public class ScriptDataSourceInitializer implements ResourceLoaderAware, Initial
this.settings.getEncoding()); this.settings.getEncoding());
} }
protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) { protected abstract void runScripts(List<Resource> resources, boolean continueOnError, String separator,
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); Charset encoding);
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);
}
private static class ScriptLocationResolver { private static class ScriptLocationResolver {

@ -14,20 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.jdbc.init; package org.springframework.boot.sql.init;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.List; 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 * @author Andy Wilkinson
* @since 2.5.0 * @since 2.5.0
*/ */
public class DataSourceInitializationSettings { public class DatabaseInitializationSettings {
private List<String> schemaLocations; private List<String> schemaLocations;

@ -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;

@ -82,7 +82,7 @@ org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
# DataSource Initializer Detectors # DataSource Initializer Detectors
org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector=\ org.springframework.boot.jdbc.init.dependency.DataSourceInitializerDetector=\
org.springframework.boot.flyway.FlywayDataSourceInitializerDetector,\ 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.liquibase.LiquibaseDataSourceInitializerDetector,\
org.springframework.boot.orm.jpa.JpaDataSourceInitializerDetector org.springframework.boot.orm.jpa.JpaDataSourceInitializerDetector

@ -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);
}
}

@ -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();
}
}

@ -14,105 +14,88 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.jdbc.init; package org.springframework.boot.sql.init;
import java.util.Arrays; 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.junit.jupiter.api.Test;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.dao.DataAccessException; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/** /**
* Tests for {@link ScriptDataSourceInitializer}. * Base class for testing {@link AbstractScriptDatabaseInitializer} implementations.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
*/ */
class ScriptDataSourceInitializerTests { public abstract class AbstractScriptDatabaseInitializerTests {
private final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class)
.url("jdbc:h2:mem:" + UUID.randomUUID()).build();
@AfterEach
void closeDataSource() {
this.dataSource.close();
}
@Test @Test
void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() { void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql")); settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql")); settings.setDataLocations(Arrays.asList("data.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue(); assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1); assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
} }
@Test @Test
void whenContinueOnErrorIsFalseThenInitializationFailsOnError() { void whenContinueOnErrorIsFalseThenInitializationFailsOnError() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("data.sql")); settings.setDataLocations(Arrays.asList("data.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase()); assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase());
} }
@Test @Test
void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() { void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setContinueOnError(true); settings.setContinueOnError(true);
settings.setDataLocations(Arrays.asList("data.sql")); settings.setDataLocations(Arrays.asList("data.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue(); assertThat(initializer.initializeDatabase()).isTrue();
} }
@Test @Test
void whenNoScriptsExistAtASchemaLocationThenInitializationFails() { void whenNoScriptsExistAtASchemaLocationThenInitializationFails() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("does-not-exist.sql")); settings.setSchemaLocations(Arrays.asList("does-not-exist.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
.withMessage("No schema scripts found at location 'does-not-exist.sql'"); .withMessage("No schema scripts found at location 'does-not-exist.sql'");
} }
@Test @Test
void whenNoScriptsExistAtADataLocationThenInitializationFails() { void whenNoScriptsExistAtADataLocationThenInitializationFails() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("does-not-exist.sql")); settings.setDataLocations(Arrays.asList("does-not-exist.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
.withMessage("No data scripts found at location 'does-not-exist.sql'"); .withMessage("No data scripts found at location 'does-not-exist.sql'");
} }
@Test @Test
void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() { void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql")); settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse(); assertThat(initializer.initializeDatabase()).isFalse();
} }
@Test @Test
void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() { void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() {
DataSourceInitializationSettings settings = new DataSourceInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql")); settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql"));
ScriptDataSourceInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse(); assertThat(initializer.initializeDatabase()).isFalse();
} }
private ScriptDataSourceInitializer createInitializer(DataSourceInitializationSettings settings) { protected abstract AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings);
return new ScriptDataSourceInitializer(this.dataSource, settings);
}
private int numberOfRows(String sql) { protected abstract int numberOfRows(String sql);
return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class);
}
} }

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,15 +16,8 @@
package smoketest.data.r2dbc; package smoketest.data.r2dbc;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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 @SpringBootApplication
public class SampleR2dbcApplication { public class SampleR2dbcApplication {
@ -33,13 +26,4 @@ public class SampleR2dbcApplication {
SpringApplication.run(SampleR2dbcApplication.class, args); 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();
};
}
} }

@ -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 (2000, 'Washington', 'DC', 'US');
INSERT INTO CITY (ID, NAME, STATE, COUNTRY) values (2001, 'San Francisco', 'CA', 'US'); INSERT INTO CITY (ID, NAME, STATE, COUNTRY) values (2001, 'San Francisco', 'CA', 'US');

@ -0,0 +1,6 @@
CREATE TABLE CITY (
id INTEGER IDENTITY PRIMARY KEY,
name VARCHAR(30),
state VARCHAR(30),
country VARCHAR(30)
);
Loading…
Cancel
Save