Move script-based DataSource initializer into spring-boot
Closes gh-25487 Closes gh-25756pull/25758/head
parent
fa336bb565
commit
5dee68c925
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* 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.autoconfigure.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* {@link InitializingBean} that performs {@link DataSource} initialization using DDL and
|
||||
* DML scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class DataSourceInitialization implements InitializingBean, ResourceLoaderAware {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceProperties properies;
|
||||
|
||||
private volatile ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DataSourceInitialization} that will initialize the given
|
||||
* {@code DataSource} using the settings from the given {@code properties}.
|
||||
* @param dataSource the DataSource to initialize
|
||||
* @param properies the properties containing the initialization settings
|
||||
*/
|
||||
public DataSourceInitialization(DataSource dataSource, DataSourceProperties properies) {
|
||||
this.dataSource = dataSource;
|
||||
this.properies = properies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
new DataSourceInitializer(this.dataSource, this.properies, this.resourceLoader).initializeDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
}
|
@ -1,196 +0,0 @@
|
||||
/*
|
||||
* 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.autoconfigure.jdbc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.boot.jdbc.DataSourceInitializationMode;
|
||||
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.jdbc.config.SortedResourcesFactoryBean;
|
||||
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
|
||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Initialize a {@link DataSource} based on a matching {@link DataSourceProperties}
|
||||
* config.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Phillip Webb
|
||||
* @author Eddú Meléndez
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class DataSourceInitializer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceProperties properties;
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link DataSource} to initialize and its matching
|
||||
* {@link DataSourceProperties configuration}.
|
||||
* @param dataSource the datasource to initialize
|
||||
* @param properties the matching configuration
|
||||
* @param resourceLoader the resource loader to use (can be null)
|
||||
*/
|
||||
public DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
|
||||
ResourceLoader resourceLoader) {
|
||||
this.dataSource = dataSource;
|
||||
this.properties = properties;
|
||||
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link DataSource} by running DDL and DML scripts.
|
||||
* @return {@code true} if one or more scripts were applied to the database, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public boolean initializeDataSource() {
|
||||
boolean initialized = createSchema();
|
||||
initialized = initSchema() && initialized;
|
||||
return initialized;
|
||||
}
|
||||
|
||||
private boolean createSchema() {
|
||||
List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
|
||||
if (!scripts.isEmpty()) {
|
||||
if (!isEnabled()) {
|
||||
logger.debug("Initialization disabled (not running DDL scripts)");
|
||||
return false;
|
||||
}
|
||||
String username = this.properties.getSchemaUsername();
|
||||
String password = this.properties.getSchemaPassword();
|
||||
runScripts(scripts, username, password);
|
||||
}
|
||||
return !scripts.isEmpty();
|
||||
}
|
||||
|
||||
private boolean initSchema() {
|
||||
List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data");
|
||||
if (!scripts.isEmpty()) {
|
||||
if (!isEnabled()) {
|
||||
logger.debug("Initialization disabled (not running data scripts)");
|
||||
return false;
|
||||
}
|
||||
String username = this.properties.getDataUsername();
|
||||
String password = this.properties.getDataPassword();
|
||||
runScripts(scripts, username, password);
|
||||
}
|
||||
return !scripts.isEmpty();
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
DataSourceInitializationMode mode = this.properties.getInitializationMode();
|
||||
if (mode == DataSourceInitializationMode.NEVER) {
|
||||
return false;
|
||||
}
|
||||
if (mode == DataSourceInitializationMode.EMBEDDED && !isEmbedded()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isEmbedded() {
|
||||
try {
|
||||
return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.debug("Could not determine if datasource is embedded", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
|
||||
if (resources != null) {
|
||||
return getResources(propertyName, resources, true);
|
||||
}
|
||||
String platform = this.properties.getPlatform();
|
||||
List<String> fallbackResources = new ArrayList<>();
|
||||
fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
|
||||
fallbackResources.add("classpath*:" + fallback + ".sql");
|
||||
return getResources(propertyName, fallbackResources, false);
|
||||
}
|
||||
|
||||
private List<Resource> getResources(String propertyName, List<String> locations, boolean validate) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
for (String location : locations) {
|
||||
for (Resource resource : doGetResources(location)) {
|
||||
if (resource.exists()) {
|
||||
resources.add(resource);
|
||||
}
|
||||
else if (validate) {
|
||||
throw new InvalidConfigurationPropertyValueException(propertyName, resource,
|
||||
"No resources were found at location '" + location + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private Resource[] doGetResources(String location) {
|
||||
try {
|
||||
SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(this.resourceLoader,
|
||||
Collections.singletonList(location));
|
||||
factory.afterPropertiesSet();
|
||||
return factory.getObject();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to load resources from " + location, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void runScripts(List<Resource> resources, String username, String password) {
|
||||
if (resources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||
populator.setContinueOnError(this.properties.isContinueOnError());
|
||||
populator.setSeparator(this.properties.getSeparator());
|
||||
if (this.properties.getSqlScriptEncoding() != null) {
|
||||
populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
|
||||
}
|
||||
for (Resource resource : resources) {
|
||||
populator.addScript(resource);
|
||||
}
|
||||
DataSource dataSource = this.dataSource;
|
||||
if (StringUtils.hasText(username) && dataSource != null) {
|
||||
dataSource = DataSourceBuilder.derivedFrom(dataSource).type(SimpleDriverDataSource.class).username(username)
|
||||
.password(password).build();
|
||||
}
|
||||
DatabasePopulatorUtils.execute(populator, dataSource);
|
||||
}
|
||||
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
/*
|
||||
* 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.autoconfigure.jdbc;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.boot.jdbc.DataSourceInitializationMode;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataSourceInitializer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DataSourceInitializerTests {
|
||||
|
||||
@Test
|
||||
void initializeEmbeddedByDefault() {
|
||||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties(), null);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
initializer.initializeDataSource();
|
||||
assertNumberOfRows(jdbcTemplate, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void initializeWithModeAlways() {
|
||||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceProperties properties = new DataSourceProperties();
|
||||
properties.setInitializationMode(DataSourceInitializationMode.ALWAYS);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties, null);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
initializer.initializeDataSource();
|
||||
assertNumberOfRows(jdbcTemplate, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNumberOfRows(JdbcTemplate jdbcTemplate, int count) {
|
||||
assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) from BAR", Integer.class)).isEqualTo(count);
|
||||
}
|
||||
|
||||
@Test
|
||||
void initializeWithModeNever() {
|
||||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceProperties properties = new DataSourceProperties();
|
||||
properties.setInitializationMode(DataSourceInitializationMode.NEVER);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties, null);
|
||||
assertThat(initializer.initializeDataSource()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void initializeOnlyEmbeddedByDefault() throws SQLException {
|
||||
DatabaseMetaData metadata = mock(DatabaseMetaData.class);
|
||||
given(metadata.getDatabaseProductName()).willReturn("MySQL");
|
||||
Connection connection = mock(Connection.class);
|
||||
given(connection.getMetaData()).willReturn(metadata);
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties(), null);
|
||||
assertThat(initializer.initializeDataSource()).isFalse();
|
||||
verify(dataSource, times(2)).getConnection();
|
||||
}
|
||||
|
||||
private HikariDataSource createDataSource() {
|
||||
return DataSourceBuilder.create().type(HikariDataSource.class).url("jdbc:h2:mem:" + UUID.randomUUID()).build();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.jdbc.init;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
/**
|
||||
* Settings for initializing a database using a JDBC {@link DataSource}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class DataSourceInitializationSettings {
|
||||
|
||||
private List<String> ddlScriptLocations;
|
||||
|
||||
private List<String> dmlScriptLocations;
|
||||
|
||||
private boolean continueOnError = false;
|
||||
|
||||
private String separator = ";";
|
||||
|
||||
private Charset encoding;
|
||||
|
||||
/**
|
||||
* Returns the locations of the DDL (schema) scripts to apply to the database.
|
||||
* @return the locations of the DDL scripts
|
||||
*/
|
||||
public List<String> getDdlScriptLocations() {
|
||||
return this.ddlScriptLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the locations of DDL (schema) scripts to apply to the database. By default,
|
||||
* initialization will fail if a location does not exist. To prevent a failure, a
|
||||
* location can be made optional by prefixing it with {@code optional:}.
|
||||
* @param ddlScriptLocations locations of the DDL scripts
|
||||
*/
|
||||
public void setDdlScriptLocations(List<String> ddlScriptLocations) {
|
||||
this.ddlScriptLocations = ddlScriptLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locations of the DML (data) scripts to apply to the database.
|
||||
* @return the locations of the DML scripts
|
||||
*/
|
||||
public List<String> getDmlScriptLocations() {
|
||||
return this.dmlScriptLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the locations of DML (data) scripts to apply to the database. By default,
|
||||
* initialization will fail if a location does not exist. To prevent a failure, a
|
||||
* location can be made optional by prefixing it with {@code optional:}.
|
||||
* @param dmlScriptLocations locations of the DML scripts
|
||||
*/
|
||||
public void setDmlScriptLocations(List<String> dmlScriptLocations) {
|
||||
this.dmlScriptLocations = dmlScriptLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to continue when an error occurs while applying a DDL or DML
|
||||
* script.
|
||||
* @return whether to continue on error
|
||||
*/
|
||||
public boolean isContinueOnError() {
|
||||
return this.continueOnError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether initialization should continue when an error occurs when applying a
|
||||
* DDL or DML script.
|
||||
* @param continueOnError whether to continue when an error occurs.
|
||||
*/
|
||||
public void setContinueOnError(boolean continueOnError) {
|
||||
this.continueOnError = continueOnError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the statement separator used in the DDL and DML scripts.
|
||||
* @return the statement separator
|
||||
*/
|
||||
public String getSeparator() {
|
||||
return this.separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the statement separator to use when reading the DDL and DML scripts.
|
||||
* @param separator statement separator used in DDL and DML scripts
|
||||
*/
|
||||
public void setSeparator(String separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoding to use when reading the DDL and DML scripts.
|
||||
* @return the script encoding
|
||||
*/
|
||||
public Charset getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encoding to use when reading the DDL and DML scripts.
|
||||
* @param encoding encoding to use when reading the DDL and DML scripts
|
||||
*/
|
||||
public void setEncoding(Charset encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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.jdbc.init;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternUtils;
|
||||
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link InitializingBean} that performs {@link DataSource} initialization using DDL and
|
||||
* DML scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class ScriptDataSourceInitializer implements ResourceLoaderAware, InitializingBean {
|
||||
|
||||
private static final String OPTIONAL_LOCATION_PREFIX = "optional:";
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceInitializationSettings settings;
|
||||
|
||||
private volatile ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ScriptDataSourceInitializer} that will initialize the given
|
||||
* {@code DataSource} using the given settings.
|
||||
* @param dataSource data source to initialize
|
||||
* @param settings initialization settings
|
||||
*/
|
||||
public ScriptDataSourceInitializer(DataSource dataSource, DataSourceInitializationSettings settings) {
|
||||
this.dataSource = dataSource;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code DataSource} that will be initialized.
|
||||
* @return the initialization data source
|
||||
*/
|
||||
protected final DataSource getDataSource() {
|
||||
return this.dataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
initializeDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database by running DDL and DML scripts.
|
||||
* @return {@code true} if one or more scripts were applied to the database, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
public boolean initializeDatabase() {
|
||||
ScriptLocationResolver locationResolver = new ScriptLocationResolver(this.resourceLoader);
|
||||
boolean initialized = applyDdlScripts(locationResolver);
|
||||
initialized = applyDmlScripts(locationResolver) || initialized;
|
||||
return initialized;
|
||||
}
|
||||
|
||||
private boolean applyDdlScripts(ScriptLocationResolver locationResolver) {
|
||||
return applyScripts(this.settings.getDdlScriptLocations(), "DDL", locationResolver);
|
||||
}
|
||||
|
||||
private boolean applyDmlScripts(ScriptLocationResolver locationResolver) {
|
||||
return applyScripts(this.settings.getDmlScriptLocations(), "DML", locationResolver);
|
||||
}
|
||||
|
||||
private boolean applyScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
|
||||
List<Resource> scripts = getScripts(locations, type, locationResolver);
|
||||
if (!scripts.isEmpty()) {
|
||||
runScripts(scripts);
|
||||
}
|
||||
return !scripts.isEmpty();
|
||||
}
|
||||
|
||||
private List<Resource> getScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
|
||||
if (CollectionUtils.isEmpty(locations)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
for (String location : locations) {
|
||||
boolean optional = location.startsWith(OPTIONAL_LOCATION_PREFIX);
|
||||
if (optional) {
|
||||
location = location.substring(OPTIONAL_LOCATION_PREFIX.length());
|
||||
}
|
||||
for (Resource resource : doGetResources(location, locationResolver)) {
|
||||
if (resource.exists()) {
|
||||
resources.add(resource);
|
||||
}
|
||||
else if (!optional) {
|
||||
throw new IllegalStateException("No " + type + " scripts found at location '" + location + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
private List<Resource> doGetResources(String location, ScriptLocationResolver locationResolver) {
|
||||
try {
|
||||
return locationResolver.resolve(location);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException("Unable to load resources from " + location, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void runScripts(List<Resource> resources) {
|
||||
if (resources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
runScripts(resources, this.settings.isContinueOnError(), this.settings.getSeparator(),
|
||||
this.settings.getEncoding());
|
||||
}
|
||||
|
||||
protected void runScripts(List<Resource> resources, boolean continueOnError, String separator, Charset encoding) {
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
|
||||
populator.setContinueOnError(continueOnError);
|
||||
populator.setSeparator(separator);
|
||||
if (encoding != null) {
|
||||
populator.setSqlScriptEncoding(encoding.name());
|
||||
}
|
||||
for (Resource resource : resources) {
|
||||
populator.addScript(resource);
|
||||
}
|
||||
DatabasePopulatorUtils.execute(populator, this.dataSource);
|
||||
}
|
||||
|
||||
private static class ScriptLocationResolver {
|
||||
|
||||
private final ResourcePatternResolver resourcePatternResolver;
|
||||
|
||||
ScriptLocationResolver(ResourceLoader resourceLoader) {
|
||||
this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
|
||||
}
|
||||
|
||||
private List<Resource> resolve(String location) throws IOException {
|
||||
List<Resource> resources = new ArrayList<>(
|
||||
Arrays.asList(this.resourcePatternResolver.getResources(location)));
|
||||
resources.sort((r1, r2) -> {
|
||||
try {
|
||||
return r1.getURL().toString().compareTo(r2.getURL().toString());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
return resources;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for initializaton of a JDBC {@code DataSource}.
|
||||
*/
|
||||
package org.springframework.boot.jdbc.init;
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.jdbc.init;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.jdbc.DataSourceBuilder;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link ScriptDataSourceInitializer}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class ScriptDataSourceInitializerTests {
|
||||
|
||||
private final HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class)
|
||||
.url("jdbc:h2:mem:" + UUID.randomUUID()).build();
|
||||
|
||||
@AfterEach
|
||||
void closeDataSource() {
|
||||
this.dataSource.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDatabaseIsInitializedThenDdlAndDmlScriptsAreApplied() {
|
||||
DataSourceInitializationSettings settings = new DataSourceInitializationSettings();
|
||||
settings.setDdlScriptLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDmlScriptLocations(Arrays.asList("data.sql"));
|
||||
ScriptDataSourceInitializer initializer = createInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThat(numberOfRows("SELECT COUNT(*) FROM EXAMPLE")).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenContinueOnErrorIsFalseThenInitializationFailsOnError() {
|
||||
DataSourceInitializationSettings settings = new DataSourceInitializationSettings();
|
||||
settings.setDmlScriptLocations(Arrays.asList("data.sql"));
|
||||
ScriptDataSourceInitializer initializer = createInitializer(settings);
|
||||
assertThatExceptionOfType(DataAccessException.class).isThrownBy(() -> initializer.initializeDatabase());
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() {
|
||||
DataSourceInitializationSettings settings = new DataSourceInitializationSettings();
|
||||
settings.setContinueOnError(true);
|
||||
settings.setDmlScriptLocations(Arrays.asList("data.sql"));
|
||||
ScriptDataSourceInitializer initializer = createInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
}
|
||||
|
||||
private ScriptDataSourceInitializer createInitializer(DataSourceInitializationSettings settings) {
|
||||
return new ScriptDataSourceInitializer(this.dataSource, settings);
|
||||
}
|
||||
|
||||
private int numberOfRows(String sql) {
|
||||
return new JdbcTemplate(this.dataSource).queryForObject(sql, Integer.class);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
INSERT INTO EXAMPLE VALUES (1, 'Andy');
|
@ -0,0 +1,4 @@
|
||||
CREATE TABLE EXAMPLE (
|
||||
id INTEGER IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(30)
|
||||
);
|
Loading…
Reference in New Issue