Reinstate mode for controlling DB initialization

Closes gh-26682
pull/26861/head
Andy Wilkinson 3 years ago
parent 1a0e008a8c
commit c52143727a

@ -16,7 +16,6 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -40,15 +39,14 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfi
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.DataSourceInitializationCondition; import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.DataSourceInitializationCondition;
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.init.DataSourceScriptDatabaseInitializer; 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.DatabaseInitializationSettings;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer;
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;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -80,6 +78,19 @@ class DataSourceInitializationConfiguration {
return fallbackLocations; 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 // Fully-qualified to work around javac bug in JDK 1.8
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false) @org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class) @org.springframework.context.annotation.Conditional(DifferentCredentialsCondition.class)
@ -96,10 +107,10 @@ class DataSourceInitializationConfiguration {
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding()); settings.setEncoding(properties.getSqlScriptEncoding());
settings.setMode(mapMode(properties.getInitializationMode()));
DataSource initializationDataSource = determineDataSource(dataSource::getObject, DataSource initializationDataSource = determineDataSource(dataSource::getObject,
properties.getSchemaUsername(), properties.getSchemaPassword()); properties.getSchemaUsername(), properties.getSchemaPassword());
return new InitializationModeDataSourceScriptDatabaseInitializer(initializationDataSource, settings, return new DataSourceScriptDatabaseInitializer(initializationDataSource, settings);
properties.getInitializationMode());
} }
@Bean @Bean
@ -111,10 +122,10 @@ class DataSourceInitializationConfiguration {
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding()); settings.setEncoding(properties.getSqlScriptEncoding());
settings.setMode(mapMode(properties.getInitializationMode()));
DataSource initializationDataSource = determineDataSource(dataSource::getObject, DataSource initializationDataSource = determineDataSource(dataSource::getObject,
properties.getDataUsername(), properties.getDataPassword()); properties.getDataUsername(), properties.getDataPassword());
return new InitializationModeDataSourceScriptDatabaseInitializer(initializationDataSource, settings, return new DataSourceScriptDatabaseInitializer(initializationDataSource, settings);
properties.getInitializationMode());
} }
static class DifferentCredentialsCondition extends AnyNestedCondition { static class DifferentCredentialsCondition extends AnyNestedCondition {
@ -154,8 +165,8 @@ class DataSourceInitializationConfiguration {
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getSqlScriptEncoding()); settings.setEncoding(properties.getSqlScriptEncoding());
return new InitializationModeDataSourceScriptDatabaseInitializer(dataSource, settings, settings.setMode(mapMode(properties.getInitializationMode()));
properties.getInitializationMode()); return new DataSourceScriptDatabaseInitializer(dataSource, settings);
} }
static class DataSourceInitializationCondition extends SpringBootCondition { static class DataSourceInitializationCondition extends SpringBootCondition {
@ -186,25 +197,4 @@ class DataSourceInitializationConfiguration {
} }
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);
}
}
}
} }

@ -392,7 +392,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB
} }
@Deprecated @Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.sql.init.enabled") @DeprecatedConfigurationProperty(replacement = "spring.sql.init.mode")
public DataSourceInitializationMode getInitializationMode() { public DataSourceInitializationMode getInitializationMode() {
return this.initializationMode; return this.initializationMode;
} }

@ -41,6 +41,7 @@ final class SettingsCreator {
settings.setContinueOnError(properties.isContinueOnError()); settings.setContinueOnError(properties.isContinueOnError());
settings.setSeparator(properties.getSeparator()); settings.setSeparator(properties.getSeparator());
settings.setEncoding(properties.getEncoding()); settings.setEncoding(properties.getEncoding());
settings.setMode(properties.getMode());
return settings; return settings;
} }

@ -20,11 +20,14 @@ 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.NoneNestedConditions;
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.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration.SqlInitializationModeCondition;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.dependency.DatabaseInitializationDependencyConfigurer; 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.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -36,11 +39,25 @@ import org.springframework.context.annotation.Import;
*/ */
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class) @ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class }) @AutoConfigureAfter({ R2dbcAutoConfiguration.class, DataSourceAutoConfiguration.class })
@EnableConfigurationProperties(SqlInitializationProperties.class) @EnableConfigurationProperties(SqlInitializationProperties.class)
@Import({ DatabaseInitializationDependencyConfigurer.class, R2dbcInitializationConfiguration.class, @Import({ DatabaseInitializationDependencyConfigurer.class, R2dbcInitializationConfiguration.class,
DataSourceInitializationConfiguration.class }) DataSourceInitializationConfiguration.class })
@ConditionalOnProperty(prefix = "spring.sql.init", name = "enabled", matchIfMissing = true)
@Conditional(SqlInitializationModeCondition.class)
public class SqlInitializationAutoConfiguration { 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 {
}
}
} }

@ -20,6 +20,7 @@ import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.sql.init.DatabaseInitializationMode;
/** /**
* {@link ConfigurationProperties Configuration properties} for initializing an SQL * {@link ConfigurationProperties Configuration properties} for initializing an SQL
@ -74,6 +75,11 @@ public class SqlInitializationProperties {
*/ */
private Charset encoding; private Charset encoding;
/**
* Mode to apply when determining whether initialization should be performed.
*/
private DatabaseInitializationMode mode = DatabaseInitializationMode.EMBEDDED;
public List<String> getSchemaLocations() { public List<String> getSchemaLocations() {
return this.schemaLocations; return this.schemaLocations;
} }
@ -138,4 +144,12 @@ public class SqlInitializationProperties {
this.encoding = encoding; this.encoding = encoding;
} }
public DatabaseInitializationMode getMode() {
return this.mode;
}
public void setMode(DatabaseInitializationMode mode) {
this.mode = mode;
}
} }

@ -1746,7 +1746,11 @@
"name": "spring.sql.init.enabled", "name": "spring.sql.init.enabled",
"type": "java.lang.Boolean", "type": "java.lang.Boolean",
"description": "Whether basic script-based initialization of an SQL database is enabled.", "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", "name": "spring.thymeleaf.prefix",

@ -61,7 +61,8 @@ class HikariDriverConfigurationFailureAnalyzerTests {
private BeanCreationException createFailure(Class<?> configuration) { private BeanCreationException createFailure(Class<?> configuration) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
TestPropertyValues.of("spring.datasource.type=" + HikariDataSource.class.getName(), 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); context.register(configuration);
try { try {
context.refresh(); context.refresh();

@ -159,7 +159,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test @Test
void testFlywaySwitchOffDdlAuto() { 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)) .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> assertThat(context).hasNotFailed()); .run((context) -> assertThat(context).hasNotFailed());
} }
@ -167,7 +167,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Test @Test
void testFlywayPlusValidation() { void testFlywayPlusValidation() {
contextRunner() 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") "spring.jpa.hibernate.ddl-auto:validate")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class)) .withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> assertThat(context).hasNotFailed()); .run((context) -> assertThat(context).hasNotFailed());

@ -27,8 +27,10 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations; 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.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration; import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer; import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer; import org.springframework.boot.r2dbc.init.R2dbcScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
@ -64,12 +66,21 @@ public class SqlInitializationAutoConfigurationTests {
} }
@Test @Test
@Deprecated
void whenConnectionFactoryIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() { void whenConnectionFactoryIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
.withPropertyValues("spring.sql.init.enabled:false") .withPropertyValues("spring.sql.init.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); .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 @Test
void whenDataSourceIsAvailableThenDataSourceInitializerIsAutoConfigured() { void whenDataSourceIsAvailableThenDataSourceInitializerIsAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
@ -77,12 +88,20 @@ public class SqlInitializationAutoConfigurationTests {
} }
@Test @Test
@Deprecated
void whenDataSourceIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() { void whenDataSourceIsAvailableAndInitializationIsDisabledThenInitializerIsNotAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withPropertyValues("spring.sql.init.enabled:false") .withPropertyValues("spring.sql.init.enabled:false")
.run((context) -> assertThat(context).doesNotHaveBean(AbstractScriptDatabaseInitializer.class)); .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 @Test
void whenDataSourceAndConnectionFactoryAreAvailableThenOnlyR2dbcInitializerIsAutoConfigured() { void whenDataSourceAndConnectionFactoryAreAvailableThenOnlyR2dbcInitializerIsAutoConfigured() {
this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class)) this.contextRunner.withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
@ -135,6 +154,11 @@ public class SqlInitializationAutoConfigurationTests {
// No-op // No-op
} }
@Override
protected boolean isEmbeddedDatabase() {
return true;
}
}; };
} }

@ -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[]. 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, 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. 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. 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[]. You can tune that behavior by setting configprop:spring.sql.init.continue-on-error[].

@ -22,6 +22,7 @@ import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean; 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.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -58,6 +59,11 @@ public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseI
return this.dataSource; return this.dataSource;
} }
@Override
protected boolean isEmbeddedDatabase() {
return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
}
@Override @Override
protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) { protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); ResourceDatabasePopulator populator = new ResourceDatabasePopulator();

@ -29,7 +29,6 @@ import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions; import io.r2dbc.spi.ConnectionFactoryOptions;
import io.r2dbc.spi.ConnectionFactoryOptions.Builder; import io.r2dbc.spi.ConnectionFactoryOptions.Builder;
import io.r2dbc.spi.ValidationDepth; import io.r2dbc.spi.ValidationDepth;
import io.r2dbc.spi.Wrapped;
import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -104,14 +103,9 @@ public final class ConnectionFactoryBuilder {
} }
private static ConnectionFactoryOptions extractOptionsIfPossible(ConnectionFactory connectionFactory) { private static ConnectionFactoryOptions extractOptionsIfPossible(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof OptionsCapableConnectionFactory) { OptionsCapableConnectionFactory optionsCapable = OptionsCapableConnectionFactory.unwrapFrom(connectionFactory);
return ((OptionsCapableConnectionFactory) connectionFactory).getOptions(); if (optionsCapable != null) {
} return optionsCapable.getOptions();
if (connectionFactory instanceof Wrapped) {
Object unwrapped = ((Wrapped<?>) connectionFactory).unwrap();
if (unwrapped instanceof ConnectionFactory) {
return extractOptionsIfPossible((ConnectionFactory) unwrapped);
}
} }
return null; return null;
} }

@ -16,6 +16,11 @@
package org.springframework.boot.r2dbc; package org.springframework.boot.r2dbc;
import java.util.function.Predicate;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -31,21 +36,25 @@ public enum EmbeddedDatabaseConnection {
/** /**
* No Connection. * No Connection.
*/ */
NONE(null, null), NONE(null, null, (options) -> false),
/** /**
* H2 Database Connection. * H2 Database Connection.
*/ */
H2("io.r2dbc.h2.H2ConnectionFactoryProvider", H2("io.r2dbc.h2.H2ConnectionFactoryProvider", "r2dbc:h2:mem:///%s?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
"r2dbc:h2:mem:///%s?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); (options) -> options.getValue(ConnectionFactoryOptions.DRIVER).equals("h2")
&& options.getValue(ConnectionFactoryOptions.PROTOCOL).equals("mem"));
private final String driverClassName; private final String driverClassName;
private final String url; private final String url;
EmbeddedDatabaseConnection(String driverClassName, String url) { private Predicate<ConnectionFactoryOptions> embedded;
EmbeddedDatabaseConnection(String driverClassName, String url, Predicate<ConnectionFactoryOptions> embedded) {
this.driverClassName = driverClassName; this.driverClassName = driverClassName;
this.url = url; this.url = url;
this.embedded = embedded;
} }
/** /**
@ -81,4 +90,27 @@ public enum EmbeddedDatabaseConnection {
return NONE; return NONE;
} }
/**
* Convenience method to determine if a given connection factory represents an
* embedded database type.
* @param connectionFactory the connection factory to interrogate
* @return true if the connection factory represents an embedded database
* @since 2.5.1
*/
public static boolean isEmbedded(ConnectionFactory connectionFactory) {
OptionsCapableConnectionFactory optionsCapable = OptionsCapableConnectionFactory.unwrapFrom(connectionFactory);
if (optionsCapable == null) {
throw new IllegalArgumentException(
"Cannot determine database's type as ConnectionFactory is not options-capable");
}
ConnectionFactoryOptions options = optionsCapable.getOptions();
for (EmbeddedDatabaseConnection candidate : values()) {
if (candidate.embedded.test(options)) {
return true;
}
}
return false;
}
} }

@ -67,4 +67,28 @@ public class OptionsCapableConnectionFactory implements Wrapped<ConnectionFactor
return this.delegate; return this.delegate;
} }
/**
* Returns, if possible, an {@code OptionsCapableConnectionFactory} by unwrapping the
* given {@code connectionFactory} as necessary. If the given
* {@code connectionFactory} does not wrap an {@code OptionsCapableConnectionFactory}
* and is not itself an {@code OptionsCapableConnectionFactory}, {@code null} is
* returned.
* @param connectionFactory the connection factory to unwrap
* @return the {@code OptionsCapableConnectionFactory} or {@code null}
* @since 2.5.1
*/
public static OptionsCapableConnectionFactory unwrapFrom(ConnectionFactory connectionFactory) {
if (connectionFactory instanceof OptionsCapableConnectionFactory) {
return (OptionsCapableConnectionFactory) connectionFactory;
}
if (connectionFactory instanceof Wrapped) {
Object unwrapped = ((Wrapped<?>) connectionFactory).unwrap();
if (unwrapped instanceof ConnectionFactory) {
return unwrapFrom((ConnectionFactory) unwrapped);
}
}
return null;
}
} }

@ -22,6 +22,7 @@ import java.util.List;
import io.r2dbc.spi.ConnectionFactory; import io.r2dbc.spi.ConnectionFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@ -51,6 +52,11 @@ public class R2dbcScriptDatabaseInitializer extends AbstractScriptDatabaseInitia
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
} }
@Override
protected boolean isEmbeddedDatabase() {
return EmbeddedDatabaseConnection.isEmbedded(this.connectionFactory);
}
@Override @Override
protected void runScripts(List<Resource> scripts, boolean continueOnError, String separator, Charset encoding) { protected void runScripts(List<Resource> scripts, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); ResourceDatabasePopulator populator = new ResourceDatabasePopulator();

@ -71,11 +71,31 @@ public abstract class AbstractScriptDatabaseInitializer implements ResourceLoade
* {@code false} * {@code false}
*/ */
public boolean initializeDatabase() { public boolean initializeDatabase() {
if (isEnabled()) {
ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader); ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
boolean initialized = applySchemaScripts(locationResolver); boolean initialized = applySchemaScripts(locationResolver);
initialized = applyDataScripts(locationResolver) || initialized; initialized = applyDataScripts(locationResolver) || initialized;
return initialized; return initialized;
} }
return false;
}
private boolean isEnabled() {
if (this.settings.getMode() == DatabaseInitializationMode.NEVER) {
return false;
}
return this.settings.getMode() == DatabaseInitializationMode.ALWAYS || isEmbeddedDatabase();
}
/**
* Returns whether the database that is to be initialized is embedded.
* @return {@code true} if the database is embedded, otherwise {@code false}
* @since 2.5.1
*/
protected boolean isEmbeddedDatabase() {
throw new IllegalStateException(
"Database initialization mode is '" + this.settings.getMode() + "' and database type is unknown");
}
private boolean applySchemaScripts(ScriptLocationResolver locationResolver) { private boolean applySchemaScripts(ScriptLocationResolver locationResolver) {
return applyScripts(this.settings.getSchemaLocations(), "schema", locationResolver); return applyScripts(this.settings.getSchemaLocations(), "schema", locationResolver);

@ -0,0 +1,43 @@
/*
* 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.sql.init;
/**
* Supported database initialization modes.
*
* @author Andy Wilkinson
* @since 2.5.1
* @see AbstractScriptDatabaseInitializer
*/
public enum DatabaseInitializationMode {
/**
* Always initialize the database.
*/
ALWAYS,
/**
* Only initialize an embedded database.
*/
EMBEDDED,
/**
* Never initialize the database.
*/
NEVER
}

@ -37,6 +37,8 @@ public class DatabaseInitializationSettings {
private Charset encoding; private Charset encoding;
private DatabaseInitializationMode mode = DatabaseInitializationMode.EMBEDDED;
/** /**
* Returns the locations of the schema (DDL) scripts to apply to the database. * Returns the locations of the schema (DDL) scripts to apply to the database.
* @return the locations of the schema scripts * @return the locations of the schema scripts
@ -123,4 +125,24 @@ public class DatabaseInitializationSettings {
this.encoding = encoding; this.encoding = encoding;
} }
/**
* Gets the mode to use when determining whether database initialization should be
* performed.
* @return the initialization mode
* @since 2.5.1
*/
public DatabaseInitializationMode getMode() {
return this.mode;
}
/**
* Sets the mode the use when determining whether database initialization should be
* performed.
* @param mode the initialization mode
* @since 2.5.1
*/
public void setMode(DatabaseInitializationMode mode) {
this.mode = mode;
}
} }

@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.init;
import java.util.UUID; import java.util.UUID;
import javax.sql.DataSource;
import com.zaxxer.hikari.HikariDataSource; import com.zaxxer.hikari.HikariDataSource;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -25,6 +27,7 @@ import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests;
import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
/** /**
@ -34,22 +37,44 @@ import org.springframework.jdbc.core.JdbcTemplate;
*/ */
class DataSourceScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests { class DataSourceScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests {
private final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class) private final HikariDataSource embeddedDataSource = DataSourceBuilder.create().type(HikariDataSource.class)
.url("jdbc:h2:mem:" + UUID.randomUUID()).build(); .url("jdbc:h2:mem:" + UUID.randomUUID()).build();
private final HikariDataSource standloneDataSource = DataSourceBuilder.create().type(HikariDataSource.class)
.url("jdbc:h2:file:" + new BuildOutput(DataSourceScriptDatabaseInitializerTests.class).getRootLocation()
.getAbsolutePath() + "/" + UUID.randomUUID())
.build();
@AfterEach @AfterEach
void closeDataSource() { void closeDataSource() {
this.dataSource.close(); this.embeddedDataSource.close();
this.standloneDataSource.close();
}
@Override
protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.embeddedDataSource, settings);
} }
@Override @Override
protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
return new DataSourceScriptDatabaseInitializer(this.dataSource, settings); DatabaseInitializationSettings settings) {
return new DataSourceScriptDatabaseInitializer(this.standloneDataSource, settings);
} }
@Override @Override
protected int numberOfRows(String sql) { protected int numberOfEmbeddedRows(String sql) {
return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class); return numberOfRows(this.embeddedDataSource, sql);
}
@Override
protected int numberOfStandaloneRows(String sql) {
return numberOfRows(this.standloneDataSource, sql);
}
private int numberOfRows(DataSource dataSource, String sql) {
return new JdbcTemplate(dataSource).queryForObject(sql, Integer.class);
} }
} }

@ -19,19 +19,23 @@ package org.springframework.boot.r2dbc;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import io.r2dbc.spi.ConnectionFactories;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/** /**
* Tests for {@link EmbeddedDatabaseConnection}. * Tests for {@link EmbeddedDatabaseConnection}.
* *
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Andy Wilkinson
*/ */
class EmbeddedDatabaseConnectionTests { class EmbeddedDatabaseConnectionTests {
@ -53,6 +57,41 @@ class EmbeddedDatabaseConnectionTests {
.isEqualTo(EmbeddedDatabaseConnection.NONE); .isEqualTo(EmbeddedDatabaseConnection.NONE);
} }
@Test
void whenH2IsInMemoryThenIsEmbeddedReturnsTrue() {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(ConnectionFactoryBuilder.withUrl("r2dbc:h2:mem:///" + UUID.randomUUID()).build())).isTrue();
}
@Test
void whenH2IsUsingFileStorageThenIsEmbeddedReturnsFalse() {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(ConnectionFactoryBuilder.withUrl("r2dbc:h2:file:///" + UUID.randomUUID()).build()))
.isFalse();
}
@Test
void whenPoolIsBasedByH2InMemoryThenIsEmbeddedReturnsTrue() {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(ConnectionFactoryBuilder.withUrl("r2dbc:pool:h2:mem:///" + UUID.randomUUID()).build()))
.isTrue();
}
@Test
void whenPoolIsBasedByH2WithFileStorageThenIsEmbeddedReturnsFalse() {
assertThat(EmbeddedDatabaseConnection
.isEmbedded(ConnectionFactoryBuilder.withUrl("r2dbc:pool:h2:file:///" + UUID.randomUUID()).build()))
.isFalse();
}
@Test
void whenConnectionFactoryIsNotOptionsCapableThenIsEmbeddedThrows() {
assertThatIllegalArgumentException()
.isThrownBy(() -> EmbeddedDatabaseConnection
.isEmbedded(ConnectionFactories.get("r2dbc:pool:h2:mem:///" + UUID.randomUUID())))
.withMessage("Cannot determine database's type as ConnectionFactory is not options-capable");
}
static Stream<Arguments> urlParameters() { static Stream<Arguments> urlParameters() {
return Stream.of(Arguments.arguments(EmbeddedDatabaseConnection.NONE, null), return Stream.of(Arguments.arguments(EmbeddedDatabaseConnection.NONE, null),
Arguments.arguments(EmbeddedDatabaseConnection.H2, Arguments.arguments(EmbeddedDatabaseConnection.H2,

@ -24,6 +24,7 @@ import org.springframework.boot.r2dbc.ConnectionFactoryBuilder;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests; import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializerTests;
import org.springframework.boot.sql.init.DatabaseInitializationSettings; import org.springframework.boot.sql.init.DatabaseInitializationSettings;
import org.springframework.boot.testsupport.BuildOutput;
import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.DatabaseClient;
/** /**
@ -33,18 +34,38 @@ import org.springframework.r2dbc.core.DatabaseClient;
*/ */
class R2dbcScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests { class R2dbcScriptDatabaseInitializerTests extends AbstractScriptDatabaseInitializerTests {
private final ConnectionFactory connectionFactory = ConnectionFactoryBuilder private final ConnectionFactory embeddedConnectionFactory = ConnectionFactoryBuilder
.withUrl("r2dbc:h2:mem:///" + UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE") .withUrl("r2dbc:h2:mem:///" + UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE")
.build(); .build();
private final ConnectionFactory standaloneConnectionFactory = ConnectionFactoryBuilder.withUrl("r2dbc:h2:file:///"
+ new BuildOutput(R2dbcScriptDatabaseInitializerTests.class).getRootLocation().getAbsolutePath() + "/"
+ UUID.randomUUID() + "?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE").build();
@Override @Override
protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
return new R2dbcScriptDatabaseInitializer(this.connectionFactory, settings); DatabaseInitializationSettings settings) {
return new R2dbcScriptDatabaseInitializer(this.embeddedConnectionFactory, settings);
} }
@Override @Override
protected int numberOfRows(String sql) { protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
return DatabaseClient.create(this.connectionFactory).sql(sql).map((row, metadata) -> row.get(0)).first() DatabaseInitializationSettings settings) {
return new R2dbcScriptDatabaseInitializer(this.standaloneConnectionFactory, settings);
}
@Override
protected int numberOfEmbeddedRows(String sql) {
return numberOfRows(this.embeddedConnectionFactory, sql);
}
@Override
protected int numberOfStandaloneRows(String sql) {
return numberOfRows(this.standaloneConnectionFactory, sql);
}
private int numberOfRows(ConnectionFactory connectionFactory, String sql) {
return DatabaseClient.create(connectionFactory).sql(sql).map((row, metadata) -> row.get(0)).first()
.map((number) -> ((Number) number).intValue()).block(); .map((number) -> ((Number) number).intValue()).block();
} }

@ -38,16 +38,16 @@ public abstract class AbstractScriptDatabaseInitializerTests {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); 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"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue(); assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1); assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
} }
@Test @Test
void whenContinueOnErrorIsFalseThenInitializationFailsOnError() { void whenContinueOnErrorIsFalseThenInitializationFailsOnError() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("data.sql")); settings.setDataLocations(Arrays.asList("data.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase()); assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase());
} }
@ -56,7 +56,7 @@ public abstract class AbstractScriptDatabaseInitializerTests {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setContinueOnError(true); settings.setContinueOnError(true);
settings.setDataLocations(Arrays.asList("data.sql")); settings.setDataLocations(Arrays.asList("data.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue(); assertThat(initializer.initializeDatabase()).isTrue();
} }
@ -64,7 +64,7 @@ public abstract class AbstractScriptDatabaseInitializerTests {
void whenNoScriptsExistAtASchemaLocationThenInitializationFails() { void whenNoScriptsExistAtASchemaLocationThenInitializationFails() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("does-not-exist.sql")); settings.setSchemaLocations(Arrays.asList("does-not-exist.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(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'");
} }
@ -73,7 +73,7 @@ public abstract class AbstractScriptDatabaseInitializerTests {
void whenNoScriptsExistAtADataLocationThenInitializationFails() { void whenNoScriptsExistAtADataLocationThenInitializationFails() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("does-not-exist.sql")); settings.setDataLocations(Arrays.asList("does-not-exist.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(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'");
} }
@ -82,7 +82,7 @@ public abstract class AbstractScriptDatabaseInitializerTests {
void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() { void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql")); settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse(); assertThat(initializer.initializeDatabase()).isFalse();
} }
@ -90,12 +90,81 @@ public abstract class AbstractScriptDatabaseInitializerTests {
void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() { void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql")); settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql"));
AbstractScriptDatabaseInitializer initializer = createInitializer(settings); AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse(); assertThat(initializer.initializeDatabase()).isFalse();
} }
protected abstract AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings); @Test
void whenModeIsNeverThenEmbeddedDatabaseIsNotInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.NEVER);
AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
}
@Test
void whenModeIsNeverThenStandaloneDatabaseIsNotInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.NEVER);
AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
}
@Test
void whenModeIsEmbeddedThenEmbeddedDatabaseIsInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.EMBEDDED);
AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
@Test
void whenModeIsEmbeddedThenStandaloneDatabaseIsNotInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.EMBEDDED);
AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isFalse();
}
@Test
void whenModeIsAlwaysThenEmbeddedDatabaseIsInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.ALWAYS);
AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
@Test
void whenModeIsAlwaysThenStandaloneDatabaseIsInitialized() {
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
settings.setSchemaLocations(Arrays.asList("schema.sql"));
settings.setDataLocations(Arrays.asList("data.sql"));
settings.setMode(DatabaseInitializationMode.ALWAYS);
AbstractScriptDatabaseInitializer initializer = createStandaloneDatabaseInitializer(settings);
assertThat(initializer.initializeDatabase()).isTrue();
assertThat(numberOfStandaloneRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
}
protected abstract AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer(
DatabaseInitializationSettings settings);
protected abstract AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer(
DatabaseInitializationSettings settings);
protected abstract int numberOfEmbeddedRows(String sql);
protected abstract int numberOfRows(String sql); protected abstract int numberOfStandaloneRows(String sql);
} }

Loading…
Cancel
Save