Skip to content

Commit

Permalink
Reinstate mode for controlling DB initialization
Browse files Browse the repository at this point in the history
Closes gh-26682
  • Loading branch information
wilkinsona committed Jun 8, 2021
1 parent 1a0e008 commit c521437
Show file tree
Hide file tree
Showing 22 changed files with 429 additions and 75 deletions.
Expand Up @@ -16,7 +16,6 @@

package org.springframework.boot.autoconfigure.jdbc;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -40,15 +39,14 @@
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.DataSourceInitializationCondition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DataSourceInitializationMode;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -80,6 +78,19 @@ private static List<String> scriptLocations(List<String> locations, String fallb
return fallbackLocations;
}

private static DatabaseInitializationMode mapMode(DataSourceInitializationMode mode) {
switch (mode) {
case ALWAYS:
return DatabaseInitializationMode.ALWAYS;
case EMBEDDED:
return DatabaseInitializationMode.EMBEDDED;
case NEVER:
return DatabaseInitializationMode.NEVER;
default:
throw new IllegalStateException("Unexpected initialization mode '" + mode + "'");
}
}

// Fully-qualified to work around javac bug in JDK 1.8
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class)
Expand All @@ -96,10 +107,10 @@ DataSourceScriptDatabaseInitializer ddlOnlyScriptDataSourceInitializer(ObjectPro
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding());
settings.setMode(mapMode(properties.getInitializationMode()));
DataSource initializationDataSource = determineDataSource(dataSource::getObject,
properties.getSchemaUsername(), properties.getSchemaPassword());
return new InitializationModeDataSourceScriptDatabaseInitializer(initializationDataSource, settings,
properties.getInitializationMode());
return new DataSourceScriptDatabaseInitializer(initializationDataSource, settings);
}

@Bean
Expand All @@ -111,10 +122,10 @@ DataSourceScriptDatabaseInitializer dmlOnlyScriptDataSourceInitializer(ObjectPro
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding());
settings.setMode(mapMode(properties.getInitializationMode()));
DataSource initializationDataSource = determineDataSource(dataSource::getObject,
properties.getDataUsername(), properties.getDataPassword());
return new InitializationModeDataSourceScriptDatabaseInitializer(initializationDataSource, settings,
properties.getInitializationMode());
return new DataSourceScriptDatabaseInitializer(initializationDataSource, settings);
}

static class DifferentCredentialsCondition extends AnyNestedCondition {
Expand Down Expand Up @@ -154,8 +165,8 @@ DataSourceScriptDatabaseInitializer scriptDataSourceInitializer(DataSource dataS
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding());
return new InitializationModeDataSourceScriptDatabaseInitializer(dataSource, settings,
properties.getInitializationMode());
settings.setMode(mapMode(properties.getInitializationMode()));
return new DataSourceScriptDatabaseInitializer(dataSource, settings);
}

static class DataSourceInitializationCondition extends SpringBootCondition {
Expand Down Expand Up @@ -186,25 +197,4 @@ public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeM

}

static class InitializationModeDataSourceScriptDatabaseInitializer extends DataSourceScriptDatabaseInitializer {

private final DataSourceInitializationMode mode;

InitializationModeDataSourceScriptDatabaseInitializer(DataSource dataSource,
DatabaseInitializationSettings settings, DataSourceInitializationMode mode) {
super(dataSource, settings);
this.mode = mode;
}

@Override
protected void runScripts(List<Resource> resources, boolean continueOnError, String separator,
Charset encoding) {
if (this.mode == DataSourceInitializationMode.ALWAYS || (this.mode == DataSourceInitializationMode.EMBEDDED
&& EmbeddedDatabaseConnection.isEmbedded(getDataSource()))) {
super.runScripts(resources, continueOnError, separator, encoding);
}
}

}

}
Expand Up @@ -392,7 +392,7 @@ public void setJndiName(String jndiName) {
}

@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.sql.init.enabled")
@DeprecatedConfigurationProperty(replacement = "spring.sql.init.mode")
public DataSourceInitializationMode getInitializationMode() {
return this.initializationMode;
}
Expand Down
Expand Up @@ -41,6 +41,7 @@ static DatabaseInitializationSettings createFrom(SqlInitializationProperties pro
settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getEncoding());
settings.setMode(properties.getMode());
return settings;
}

Expand Down
Expand Up @@ -20,11 +20,14 @@
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.NoneNestedConditions;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration.SqlInitializationModeCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

Expand All @@ -36,11 +39,25 @@
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class })
@EnableConfigurationProperties(SqlInitializationProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, R2dbcInitializationConfiguration.class,
DataSourceInitializationConfiguration.class })
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@Conditional(SqlInitializationModeCondition.class)
public class SqlInitializationAutoConfiguration {

static class SqlInitializationModeCondition extends NoneNestedConditions {

SqlInitializationModeCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}

@ConditionalOnProperty(prefix = "spring.sql.init", name = "mode", havingValue = "never")
static class ModeIsNever {

}

}

}
Expand Up @@ -20,6 +20,7 @@
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;

/**
* {@link ConfigurationProperties Configuration properties} for initializing an SQL
Expand Down Expand Up @@ -74,6 +75,11 @@ public class SqlInitializationProperties {
*/
private Charset encoding;

/**
* Mode to apply when determining whether initialization should be performed.
*/
private DatabaseInitializationMode mode = DatabaseInitializationMode.EMBEDDED;

public List<String> getSchemaLocations() {
return this.schemaLocations;
}
Expand Down Expand Up @@ -138,4 +144,12 @@ public void setEncoding(Charset encoding) {
this.encoding = encoding;
}

public DatabaseInitializationMode getMode() {
return this.mode;
}

public void setMode(DatabaseInitializationMode mode) {
this.mode = mode;
}

}
Expand Up @@ -1746,7 +1746,11 @@
"name": "spring.sql.init.enabled",
"type": "java.lang.Boolean",
"description": "Whether basic script-based initialization of an SQL database is enabled.",
"defaultValue": true
"defaultValue": true,
"deprecation": {
"replacement": "spring.sql.init.mode",
"level": "error"
}
},
{
"name": "spring.thymeleaf.prefix",
Expand Down
Expand Up @@ -61,7 +61,8 @@ private FailureAnalysis performAnalysis(Class<?> configuration) {
private BeanCreationException createFailure(Class<?> configuration) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.type=" + HikariDataSource.class.getName(),
"spring.datasource.hikari.data-source-class-name=com.example.Foo").applyTo(context);
"spring.datasource.hikari.data-source-class-name=com.example.Foo", "spring.sql.init.mode=always")
.applyTo(context);
context.register(configuration);
try {
context.refresh();
Expand Down
Expand Up @@ -159,15 +159,15 @@ void testDmlScriptRunsEarly() {

@Test
void testFlywaySwitchOffDdlAuto() {
contextRunner().withPropertyValues("spring.sql.init.enabled:false", "spring.flyway.locations:classpath:db/city")
contextRunner().withPropertyValues("spring.sql.init.mode:never", "spring.flyway.locations:classpath:db/city")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> assertThat(context).hasNotFailed());
}

@Test
void testFlywayPlusValidation() {
contextRunner()
.withPropertyValues("spring.sql.init.enabled:false", "spring.flyway.locations:classpath:db/city",
.withPropertyValues("spring.sql.init.mode:never", "spring.flyway.locations:classpath:db/city",
"spring.jpa.hibernate.ddl-auto:validate")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> assertThat(context).hasNotFailed());
Expand Down
Expand Up @@ -27,8 +27,10 @@
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
Expand Down Expand Up @@ -64,25 +66,42 @@ void whenConnectionFactoryIsAvailableThenR2dbcInitializerIsAutoConfigured() {
}

@Test
@Deprecated
void whenConnectionFactoryIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.withPropertyValues("spring.sql.init.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class));
}

@Test
void whenConnectionFactoryIsAvailableAndModeIsNeverThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
.withPropertyValues("spring.sql.init.mode:never")
.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
@Deprecated
void whenDataSourceIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.sql.init.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class));
}

@Test
void whenDataSourceIsAvailableAndModeIsNeverThenThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.sql.init.mode:never")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class));
}

@Test
void whenDataSourceAndConnectionFactoryAreAvailableThenOnlyR2dbcInitializerIsAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
Expand Down Expand Up @@ -135,6 +154,11 @@ protected void runScripts(List<Resource> resources, boolean continueOnError, Str
// No-op
}

@Override
protected boolean isEmbeddedDatabase() {
return true;
}

};
}

Expand Down
Expand Up @@ -43,7 +43,9 @@ It loads SQL from the standard root classpath locations: `schema.sql` and `data.
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, SQL database initialization is only performed when using an embedded in-memory database.
To always initialize an SQL database, irrespective of its type, set configprop:spring.sql.init.mode[] to `always`.
Similarly, to disable initialization, set configprop:spring.sql.init.mode[] to `never`.
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[].
Expand Down
Expand Up @@ -22,6 +22,7 @@
import javax.sql.DataSource;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.core.io.Resource;
Expand Down Expand Up @@ -58,6 +59,11 @@ protected final DataSource getDataSource() {
return this.dataSource;
}

@Override
protected boolean isEmbeddedDatabase() {
return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
}

@Override
protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
Expand Down
Expand Up @@ -29,7 +29,6 @@
import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.ValidationDepth;
import io.r2dbc.spi.Wrapped;

import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -104,14 +103,9 @@ public static ConnectionFactoryBuilder derivefrom(ConnectionFactory connectionFa
}

private static ConnectionFactoryOptions extractOptionsIfPossible(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof OptionsCapableConnectionFactory) {
return ((OptionsCapableConnectionFactory) connectionFactory).getOptions();
}
if (connectionFactory instanceof Wrapped) {
Object unwrapped = ((Wrapped<?>) connectionFactory).unwrap();
if (unwrapped instanceof ConnectionFactory) {
return extractOptionsIfPossible((ConnectionFactory) unwrapped);
}
OptionsCapableConnectionFactory optionsCapable = OptionsCapableConnectionFactory.unwrapFrom(connectionFactory);
if (optionsCapable != null) {
return optionsCapable.getOptions();
}
return null;
}
Expand Down

0 comments on commit c521437

Please sign in to comment.