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.
Original file line number Diff line number Diff line change
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);
}
}

}

}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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 {

}

}

}
Original file line number Diff line number Diff line change
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;
}

}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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.