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;
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<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
@DeprecatedConfigurationProperty(replacement = "spring.sql.init.enabled")
@DeprecatedConfigurationProperty(replacement = "spring.sql.init.mode")
public DataSourceInitializationMode getInitializationMode() {
return this.initializationMode;
}

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

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

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

@ -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",

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

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

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

@ -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[].

@ -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<Resource> resources, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();

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

@ -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<ConnectionFactoryOptions> embedded;
EmbeddedDatabaseConnection(String driverClassName, String url, Predicate<ConnectionFactoryOptions> 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;
}
}

@ -67,4 +67,28 @@ public class OptionsCapableConnectionFactory implements Wrapped<ConnectionFactor
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 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<Resource> scripts, boolean continueOnError, String separator, Charset encoding) {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();

@ -71,11 +71,31 @@ public abstract class AbstractScriptDatabaseInitializer implements ResourceLoade
* {@code false}
*/
public boolean initializeDatabase() {
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) {
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 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;
}
}

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

@ -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<Arguments> urlParameters() {
return Stream.of(Arguments.arguments(EmbeddedDatabaseConnection.NONE, null),
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.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();
}

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

Loading…
Cancel
Save