From c52143727a9c35b656f2f0d5298344e876785404 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 8 Jun 2021 11:19:41 +0100 Subject: [PATCH] Reinstate mode for controlling DB initialization Closes gh-26682 --- ...DataSourceInitializationConfiguration.java | 50 +++++------ .../jdbc/DataSourceProperties.java | 2 +- .../sql/init/SettingsCreator.java | 1 + .../SqlInitializationAutoConfiguration.java | 19 +++- .../sql/init/SqlInitializationProperties.java | 14 +++ ...itional-spring-configuration-metadata.json | 6 +- ...iverConfigurationFailureAnalyzerTests.java | 3 +- .../HibernateJpaAutoConfigurationTests.java | 4 +- ...lInitializationAutoConfigurationTests.java | 24 +++++ .../asciidoc/howto/data-initialization.adoc | 4 +- .../DataSourceScriptDatabaseInitializer.java | 6 ++ .../boot/r2dbc/ConnectionFactoryBuilder.java | 12 +-- .../r2dbc/EmbeddedDatabaseConnection.java | 40 ++++++++- .../OptionsCapableConnectionFactory.java | 24 +++++ .../init/R2dbcScriptDatabaseInitializer.java | 6 ++ .../AbstractScriptDatabaseInitializer.java | 28 +++++- .../sql/init/DatabaseInitializationMode.java | 43 +++++++++ .../init/DatabaseInitializationSettings.java | 22 +++++ ...aSourceScriptDatabaseInitializerTests.java | 37 ++++++-- .../EmbeddedDatabaseConnectionTests.java | 39 ++++++++ .../R2dbcScriptDatabaseInitializerTests.java | 31 +++++-- ...bstractScriptDatabaseInitializerTests.java | 89 ++++++++++++++++--- 22 files changed, 429 insertions(+), 75 deletions(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationMode.java diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java index 4b4bd76bbf..73059859fc 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java @@ -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; @@ -40,15 +39,14 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationConfi 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; @@ -80,6 +78,19 @@ class DataSourceInitializationConfiguration { 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) @@ -96,10 +107,10 @@ class DataSourceInitializationConfiguration { 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 @@ -111,10 +122,10 @@ class DataSourceInitializationConfiguration { 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 { @@ -154,8 +165,8 @@ class DataSourceInitializationConfiguration { 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 { @@ -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 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); - } - } - - } - } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java index 75b6110357..d50622fc8f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.java @@ -392,7 +392,7 @@ public class DataSourceProperties implements BeanClassLoaderAware, InitializingB } @Deprecated - @DeprecatedConfigurationProperty(replacement = "spring.sql.init.enabled") + @DeprecatedConfigurationProperty(replacement = "spring.sql.init.mode") public DataSourceInitializationMode getInitializationMode() { return this.initializationMode; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java index cec633db6d..c4e276de4d 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SettingsCreator.java @@ -41,6 +41,7 @@ final class SettingsCreator { settings.setContinueOnError(properties.isContinueOnError()); settings.setSeparator(properties.getSeparator()); settings.setEncoding(properties.getEncoding()); + settings.setMode(properties.getMode()); return settings; } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java index 909adbc23e..6ec161671a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfiguration.java @@ -20,11 +20,14 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.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; @@ -36,11 +39,25 @@ import org.springframework.context.annotation.Import; */ @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 { + + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationProperties.java index 174f4ee3c8..6d11f61816 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationProperties.java @@ -20,6 +20,7 @@ import java.nio.charset.Charset; 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 @@ -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 getSchemaLocations() { return this.schemaLocations; } @@ -138,4 +144,12 @@ public class SqlInitializationProperties { this.encoding = encoding; } + public DatabaseInitializationMode getMode() { + return this.mode; + } + + public void setMode(DatabaseInitializationMode mode) { + this.mode = mode; + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 3c4bf7e698..be60355dd4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDriverConfigurationFailureAnalyzerTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDriverConfigurationFailureAnalyzerTests.java index 83add1d50a..0937cdfbb3 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDriverConfigurationFailureAnalyzerTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/HikariDriverConfigurationFailureAnalyzerTests.java @@ -61,7 +61,8 @@ class HikariDriverConfigurationFailureAnalyzerTests { 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(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java index 4eaf1c6f31..36813a7f1f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfigurationTests.java @@ -159,7 +159,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes @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()); } @@ -167,7 +167,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes @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()); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java index 2f2fa43193..66619c9e7e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/sql/init/SqlInitializationAutoConfigurationTests.java @@ -27,8 +27,10 @@ import org.junit.jupiter.api.Test; 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; @@ -64,12 +66,21 @@ public class SqlInitializationAutoConfigurationTests { } @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)) @@ -77,12 +88,20 @@ public class SqlInitializationAutoConfigurationTests { } @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)) @@ -135,6 +154,11 @@ public class SqlInitializationAutoConfigurationTests { // No-op } + @Override + protected boolean isEmbeddedDatabase() { + return true; + } + }; } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc index 9f89371dda..a1ac1d7219 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/data-initialization.adoc @@ -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[]. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java index d453c7a4be..6fe2993ac9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializer.java @@ -22,6 +22,7 @@ import java.util.List; 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; @@ -58,6 +59,11 @@ public class DataSourceScriptDatabaseInitializer extends AbstractScriptDatabaseI return this.dataSource; } + @Override + protected boolean isEmbeddedDatabase() { + return EmbeddedDatabaseConnection.isEmbedded(this.dataSource); + } + @Override protected void runScripts(List resources, boolean continueOnError, String separator, Charset encoding) { ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java index 5e1dfc5caf..e51630d48e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/ConnectionFactoryBuilder.java @@ -29,7 +29,6 @@ import io.r2dbc.spi.ConnectionFactory; 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; @@ -104,14 +103,9 @@ public final class ConnectionFactoryBuilder { } 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; } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnection.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnection.java index 18b664d2a6..2012a7d639 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnection.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnection.java @@ -16,6 +16,11 @@ 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.ClassUtils; @@ -31,21 +36,25 @@ public enum EmbeddedDatabaseConnection { /** * No Connection. */ - NONE(null, null), + NONE(null, null, (options) -> false), /** * H2 Database Connection. */ - H2("io.r2dbc.h2.H2ConnectionFactoryProvider", - "r2dbc:h2:mem:///%s?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE"); + H2("io.r2dbc.h2.H2ConnectionFactoryProvider", "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 url; - EmbeddedDatabaseConnection(String driverClassName, String url) { + private Predicate embedded; + + EmbeddedDatabaseConnection(String driverClassName, String url, Predicate embedded) { this.driverClassName = driverClassName; this.url = url; + this.embedded = embedded; } /** @@ -81,4 +90,27 @@ public enum EmbeddedDatabaseConnection { 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; + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/OptionsCapableConnectionFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/OptionsCapableConnectionFactory.java index 38f55be48d..0d663b783a 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/OptionsCapableConnectionFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/OptionsCapableConnectionFactory.java @@ -67,4 +67,28 @@ public class OptionsCapableConnectionFactory implements Wrapped) connectionFactory).unwrap(); + if (unwrapped instanceof ConnectionFactory) { + return unwrapFrom((ConnectionFactory) unwrapped); + } + } + return null; + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java index fedfea6eea..2a0de59324 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializer.java @@ -22,6 +22,7 @@ import java.util.List; import io.r2dbc.spi.ConnectionFactory; 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.DatabaseInitializationSettings; import org.springframework.core.io.Resource; @@ -51,6 +52,11 @@ public class R2dbcScriptDatabaseInitializer extends AbstractScriptDatabaseInitia this.connectionFactory = connectionFactory; } + @Override + protected boolean isEmbeddedDatabase() { + return EmbeddedDatabaseConnection.isEmbedded(this.connectionFactory); + } + @Override protected void runScripts(List scripts, boolean continueOnError, String separator, Charset encoding) { ResourceDatabasePopulator populator = new ResourceDatabasePopulator(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java index cdb3630688..e170acc3c4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializer.java @@ -71,10 +71,30 @@ public abstract class AbstractScriptDatabaseInitializer implements ResourceLoade * {@code false} */ public boolean initializeDatabase() { - ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader); - boolean initialized = applySchemaScripts(locationResolver); - initialized = applyDataScripts(locationResolver) || initialized; - return initialized; + if (isEnabled()) { + ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader); + boolean initialized = applySchemaScripts(locationResolver); + initialized = applyDataScripts(locationResolver) || 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) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationMode.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationMode.java new file mode 100644 index 0000000000..ed360a3f6e --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationMode.java @@ -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 + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java index 62193407c2..bc2a1ccf18 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/sql/init/DatabaseInitializationSettings.java @@ -37,6 +37,8 @@ public class DatabaseInitializationSettings { private Charset encoding; + private DatabaseInitializationMode mode = DatabaseInitializationMode.EMBEDDED; + /** * Returns the locations of the schema (DDL) scripts to apply to the database. * @return the locations of the schema scripts @@ -123,4 +125,24 @@ public class DatabaseInitializationSettings { 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; + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java index 708c567565..428f031c65 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/jdbc/init/DataSourceScriptDatabaseInitializerTests.java @@ -18,6 +18,8 @@ package org.springframework.boot.jdbc.init; import java.util.UUID; +import javax.sql.DataSource; + import com.zaxxer.hikari.HikariDataSource; 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.AbstractScriptDatabaseInitializerTests; import org.springframework.boot.sql.init.DatabaseInitializationSettings; +import org.springframework.boot.testsupport.BuildOutput; import org.springframework.jdbc.core.JdbcTemplate; /** @@ -34,22 +37,44 @@ import org.springframework.jdbc.core.JdbcTemplate; */ 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(); + private final HikariDataSource standloneDataSource = DataSourceBuilder.create().type(HikariDataSource.class) + .url("jdbc:h2:file:" + new BuildOutput(DataSourceScriptDatabaseInitializerTests.class).getRootLocation() + .getAbsolutePath() + "/" + UUID.randomUUID()) + .build(); + @AfterEach void closeDataSource() { - this.dataSource.close(); + this.embeddedDataSource.close(); + this.standloneDataSource.close(); + } + + @Override + protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer( + DatabaseInitializationSettings settings) { + return new DataSourceScriptDatabaseInitializer(this.embeddedDataSource, settings); } @Override - protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { - return new DataSourceScriptDatabaseInitializer(this.dataSource, settings); + protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer( + DatabaseInitializationSettings settings) { + return new DataSourceScriptDatabaseInitializer(this.standloneDataSource, settings); } @Override - protected int numberOfRows(String sql) { - return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class); + protected int numberOfEmbeddedRows(String sql) { + 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); } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnectionTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnectionTests.java index a6ca5d2f93..54729a2903 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnectionTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/EmbeddedDatabaseConnectionTests.java @@ -19,19 +19,23 @@ package org.springframework.boot.r2dbc; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; +import java.util.UUID; import java.util.stream.Stream; +import io.r2dbc.spi.ConnectionFactories; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * Tests for {@link EmbeddedDatabaseConnection}. * * @author Stephane Nicoll + * @author Andy Wilkinson */ class EmbeddedDatabaseConnectionTests { @@ -53,6 +57,41 @@ class EmbeddedDatabaseConnectionTests { .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 urlParameters() { return Stream.of(Arguments.arguments(EmbeddedDatabaseConnection.NONE, null), Arguments.arguments(EmbeddedDatabaseConnection.H2, diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java index 2bc81ff5b2..cc060a1481 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/r2dbc/init/R2dbcScriptDatabaseInitializerTests.java @@ -24,6 +24,7 @@ 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.boot.testsupport.BuildOutput; import org.springframework.r2dbc.core.DatabaseClient; /** @@ -33,18 +34,38 @@ import org.springframework.r2dbc.core.DatabaseClient; */ 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") .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 - protected AbstractScriptDatabaseInitializer createInitializer(DatabaseInitializationSettings settings) { - return new R2dbcScriptDatabaseInitializer(this.connectionFactory, settings); + protected AbstractScriptDatabaseInitializer createEmbeddedDatabaseInitializer( + DatabaseInitializationSettings settings) { + return new R2dbcScriptDatabaseInitializer(this.embeddedConnectionFactory, settings); } @Override - protected int numberOfRows(String sql) { - return DatabaseClient.create(this.connectionFactory).sql(sql).map((row, metadata) -> row.get(0)).first() + protected AbstractScriptDatabaseInitializer createStandaloneDatabaseInitializer( + 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(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java index cb1e87d5dc..664ba215b8 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/sql/init/AbstractScriptDatabaseInitializerTests.java @@ -38,16 +38,16 @@ public abstract class AbstractScriptDatabaseInitializerTests { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("schema.sql")); settings.setDataLocations(Arrays.asList("data.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThat(initializer.initializeDatabase()).isTrue(); - assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1); + assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1); } @Test void whenContinueOnErrorIsFalseThenInitializationFailsOnError() { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("data.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase()); } @@ -56,7 +56,7 @@ public abstract class AbstractScriptDatabaseInitializerTests { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setContinueOnError(true); settings.setDataLocations(Arrays.asList("data.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThat(initializer.initializeDatabase()).isTrue(); } @@ -64,7 +64,7 @@ public abstract class AbstractScriptDatabaseInitializerTests { void whenNoScriptsExistAtASchemaLocationThenInitializationFails() { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("does-not-exist.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) .withMessage("No schema scripts found at location 'does-not-exist.sql'"); } @@ -73,7 +73,7 @@ public abstract class AbstractScriptDatabaseInitializerTests { void whenNoScriptsExistAtADataLocationThenInitializationFails() { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("does-not-exist.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase) .withMessage("No data scripts found at location 'does-not-exist.sql'"); } @@ -82,7 +82,7 @@ public abstract class AbstractScriptDatabaseInitializerTests { void whenNoScriptsExistAtAnOptionalSchemaLocationThenInitializationSucceeds() { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); assertThat(initializer.initializeDatabase()).isFalse(); } @@ -90,12 +90,81 @@ public abstract class AbstractScriptDatabaseInitializerTests { void whenNoScriptsExistAtAnOptionalDataLocationThenInitializationSucceeds() { DatabaseInitializationSettings settings = new DatabaseInitializationSettings(); settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql")); - AbstractScriptDatabaseInitializer initializer = createInitializer(settings); + AbstractScriptDatabaseInitializer initializer = createEmbeddedDatabaseInitializer(settings); 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); }