Create spring-boot-sql module
This commit is contained in:
committed by
Phillip Webb
parent
e288c81b7b
commit
837cf467e0
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.init;
|
||||
|
||||
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
|
||||
/**
|
||||
* Marker interface for a script-based database initializer that initializes the
|
||||
* application's database.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
* @see AbstractScriptDatabaseInitializer
|
||||
*/
|
||||
@ImportRuntimeHints(SqlInitializationScriptsRuntimeHints.class)
|
||||
public interface ApplicationScriptDatabaseInitializer {
|
||||
|
||||
/**
|
||||
* Adapts {@link SqlInitializationProperties} to
|
||||
* {@link DatabaseInitializationSettings}.
|
||||
* @param properties the properties to adapt
|
||||
* @return the settings
|
||||
*/
|
||||
static DatabaseInitializationSettings getSettings(SqlInitializationProperties properties) {
|
||||
return SettingsCreator.createFrom(properties);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.init;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
|
||||
/**
|
||||
* Condition that matches when {@code spring.sql.init.mode} is set to a value other than
|
||||
* {@link DatabaseInitializationMode#NEVER}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Conditional(OnSqlInitializationCondition.class)
|
||||
public @interface ConditionalOnSqlInitialization {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.init;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationMode;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Condition that checks if the database initialization of a particular component should
|
||||
* be considered.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.6.2
|
||||
* @see DatabaseInitializationMode
|
||||
*/
|
||||
public abstract class OnDatabaseInitializationCondition extends SpringBootCondition {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String[] propertyNames;
|
||||
|
||||
/**
|
||||
* Create a new instance with the name of the component and the property names to
|
||||
* check, in order. If a property is set, its value is used to determine the outcome
|
||||
* and remaining properties are not tested.
|
||||
* @param name the name of the component
|
||||
* @param propertyNames the properties to check (in order)
|
||||
*/
|
||||
protected OnDatabaseInitializationCondition(String name, String... propertyNames) {
|
||||
this.name = name;
|
||||
this.propertyNames = propertyNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
Environment environment = context.getEnvironment();
|
||||
String propertyName = getConfiguredProperty(environment);
|
||||
DatabaseInitializationMode mode = getDatabaseInitializationMode(environment, propertyName);
|
||||
boolean match = match(mode);
|
||||
String messagePrefix = (propertyName != null) ? propertyName : "default value";
|
||||
return new ConditionOutcome(match, ConditionMessage.forCondition(this.name + "Database Initialization")
|
||||
.because(messagePrefix + " is " + mode));
|
||||
}
|
||||
|
||||
private boolean match(DatabaseInitializationMode mode) {
|
||||
return !mode.equals(DatabaseInitializationMode.NEVER);
|
||||
}
|
||||
|
||||
private DatabaseInitializationMode getDatabaseInitializationMode(Environment environment, String propertyName) {
|
||||
if (StringUtils.hasText(propertyName)) {
|
||||
String candidate = environment.getProperty(propertyName, "embedded").toUpperCase(Locale.ENGLISH);
|
||||
if (StringUtils.hasText(candidate)) {
|
||||
return DatabaseInitializationMode.valueOf(candidate);
|
||||
}
|
||||
}
|
||||
return DatabaseInitializationMode.EMBEDDED;
|
||||
}
|
||||
|
||||
private String getConfiguredProperty(Environment environment) {
|
||||
for (String propertyName : this.propertyNames) {
|
||||
if (environment.containsProperty(propertyName)) {
|
||||
return propertyName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.init;
|
||||
|
||||
import org.springframework.context.annotation.Condition;
|
||||
|
||||
/**
|
||||
* {@link Condition} implementation for {@link ConditionalOnSqlInitialization}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class OnSqlInitializationCondition extends OnDatabaseInitializationCondition {
|
||||
|
||||
OnSqlInitializationCondition() {
|
||||
super("SQL Initialization", "spring.sql.init.mode");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.autoconfigure.init;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
||||
|
||||
/**
|
||||
* Helpers class for creating {@link DatabaseInitializationSettings} from
|
||||
* {@link SqlInitializationProperties}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
final class SettingsCreator {
|
||||
|
||||
private SettingsCreator() {
|
||||
}
|
||||
|
||||
static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings
|
||||
.setSchemaLocations(scriptLocations(properties.getSchemaLocations(), "schema", properties.getPlatform()));
|
||||
settings.setDataLocations(scriptLocations(properties.getDataLocations(), "data", properties.getPlatform()));
|
||||
settings.setContinueOnError(properties.isContinueOnError());
|
||||
settings.setSeparator(properties.getSeparator());
|
||||
settings.setEncoding(properties.getEncoding());
|
||||
settings.setMode(properties.getMode());
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static List<String> scriptLocations(List<String> locations, String fallback, String platform) {
|
||||
if (locations != null) {
|
||||
return locations;
|
||||
}
|
||||
List<String> fallbackLocations = new ArrayList<>();
|
||||
fallbackLocations.add("optional:classpath*:" + fallback + "-" + platform + ".sql");
|
||||
fallbackLocations.add("optional:classpath*:" + fallback + ".sql");
|
||||
return fallbackLocations;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.autoconfigure.init;
|
||||
|
||||
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
|
||||
* database.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@ConfigurationProperties("spring.sql.init")
|
||||
public class SqlInitializationProperties {
|
||||
|
||||
/**
|
||||
* Locations of the schema (DDL) scripts to apply to the database.
|
||||
*/
|
||||
private List<String> schemaLocations;
|
||||
|
||||
/**
|
||||
* Locations of the data (DML) scripts to apply to the database.
|
||||
*/
|
||||
private List<String> dataLocations;
|
||||
|
||||
/**
|
||||
* Platform to use in the default schema or data script locations,
|
||||
* schema-${platform}.sql and data-${platform}.sql.
|
||||
*/
|
||||
private String platform = "all";
|
||||
|
||||
/**
|
||||
* Username of the database to use when applying initialization scripts (if
|
||||
* different).
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Password of the database to use when applying initialization scripts (if
|
||||
* different).
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Whether initialization should continue when an error occurs.
|
||||
*/
|
||||
private boolean continueOnError = false;
|
||||
|
||||
/**
|
||||
* Statement separator in the schema and data scripts.
|
||||
*/
|
||||
private String separator = ";";
|
||||
|
||||
/**
|
||||
* Encoding of the schema and data scripts.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
public void setSchemaLocations(List<String> schemaLocations) {
|
||||
this.schemaLocations = schemaLocations;
|
||||
}
|
||||
|
||||
public List<String> getDataLocations() {
|
||||
return this.dataLocations;
|
||||
}
|
||||
|
||||
public void setDataLocations(List<String> dataLocations) {
|
||||
this.dataLocations = dataLocations;
|
||||
}
|
||||
|
||||
public String getPlatform() {
|
||||
return this.platform;
|
||||
}
|
||||
|
||||
public void setPlatform(String platform) {
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public boolean isContinueOnError() {
|
||||
return this.continueOnError;
|
||||
}
|
||||
|
||||
public void setContinueOnError(boolean continueOnError) {
|
||||
this.continueOnError = continueOnError;
|
||||
}
|
||||
|
||||
public String getSeparator() {
|
||||
return this.separator;
|
||||
}
|
||||
|
||||
public void setSeparator(String separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
public Charset getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
public void setEncoding(Charset encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public DatabaseInitializationMode getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
public void setMode(DatabaseInitializationMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.autoconfigure.init;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||
|
||||
/**
|
||||
* {@link RuntimeHintsRegistrar} for SQL initialization scripts.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class SqlInitializationScriptsRuntimeHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
|
||||
hints.resources().registerPattern("schema.sql").registerPattern("schema-*.sql");
|
||||
hints.resources().registerPattern("data.sql").registerPattern("data-*.sql");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for basic script-based initialization of an SQL database.
|
||||
*/
|
||||
package org.springframework.boot.sql.autoconfigure.init;
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Base class for an {@link InitializingBean} that performs SQL database initialization
|
||||
* using schema (DDL) and data (DML) scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public abstract class AbstractScriptDatabaseInitializer implements ResourceLoaderAware, InitializingBean {
|
||||
|
||||
private static final String OPTIONAL_LOCATION_PREFIX = "optional:";
|
||||
|
||||
private final DatabaseInitializationSettings settings;
|
||||
|
||||
private volatile ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractScriptDatabaseInitializer} that will initialize the
|
||||
* database using the given settings.
|
||||
* @param settings initialization settings
|
||||
*/
|
||||
protected AbstractScriptDatabaseInitializer(DatabaseInitializationSettings settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
initializeDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the database by applying schema and data 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 = applySchemaScripts(locationResolver);
|
||||
return applyDataScripts(locationResolver) || initialized;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private boolean applyDataScripts(ScriptLocationResolver locationResolver) {
|
||||
return applyScripts(this.settings.getDataLocations(), "data", locationResolver);
|
||||
}
|
||||
|
||||
private boolean applyScripts(List<String> locations, String type, ScriptLocationResolver locationResolver) {
|
||||
List<Resource> scripts = getScripts(locations, type, locationResolver);
|
||||
if (!scripts.isEmpty() && isEnabled()) {
|
||||
runScripts(scripts);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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.isReadable()) {
|
||||
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) {
|
||||
runScripts(new Scripts(resources).continueOnError(this.settings.isContinueOnError())
|
||||
.separator(this.settings.getSeparator())
|
||||
.encoding(this.settings.getEncoding()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database by running the given {@code scripts}.
|
||||
* @param scripts the scripts to run
|
||||
* @since 3.0.0
|
||||
*/
|
||||
protected abstract void runScripts(Scripts scripts);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Scripts to be used to initialize the database.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public static class Scripts implements Iterable<Resource> {
|
||||
|
||||
private final List<Resource> resources;
|
||||
|
||||
private boolean continueOnError;
|
||||
|
||||
private String separator = ";";
|
||||
|
||||
private Charset encoding;
|
||||
|
||||
public Scripts(List<Resource> resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Resource> iterator() {
|
||||
return this.resources.iterator();
|
||||
}
|
||||
|
||||
public Scripts continueOnError(boolean continueOnError) {
|
||||
this.continueOnError = continueOnError;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isContinueOnError() {
|
||||
return this.continueOnError;
|
||||
}
|
||||
|
||||
public Scripts separator(String separator) {
|
||||
this.separator = separator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getSeparator() {
|
||||
return this.separator;
|
||||
}
|
||||
|
||||
public Scripts encoding(Charset encoding) {
|
||||
this.encoding = encoding;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Charset getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Settings for initializing an SQL database.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class DatabaseInitializationSettings {
|
||||
|
||||
private List<String> schemaLocations;
|
||||
|
||||
private List<String> dataLocations;
|
||||
|
||||
private boolean continueOnError;
|
||||
|
||||
private String separator = ";";
|
||||
|
||||
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
|
||||
*/
|
||||
public List<String> getSchemaLocations() {
|
||||
return this.schemaLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the locations of schema (DDL) 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 schemaLocations locations of the schema scripts
|
||||
*/
|
||||
public void setSchemaLocations(List<String> schemaLocations) {
|
||||
this.schemaLocations = schemaLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locations of data (DML) scripts to apply to the database.
|
||||
* @return the locations of the data scripts
|
||||
*/
|
||||
public List<String> getDataLocations() {
|
||||
return this.dataLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the locations of data (DML) 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 dataLocations locations of the data scripts
|
||||
*/
|
||||
public void setDataLocations(List<String> dataLocations) {
|
||||
this.dataLocations = dataLocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to continue when an error occurs while applying a schema or data
|
||||
* 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
|
||||
* schema or data 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 schema and data scripts.
|
||||
* @return the statement separator
|
||||
*/
|
||||
public String getSeparator() {
|
||||
return this.separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the statement separator to use when reading the schema and data scripts.
|
||||
* @param separator statement separator used in the schema and data scripts
|
||||
*/
|
||||
public void setSeparator(String separator) {
|
||||
this.separator = separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoding to use when reading the schema and data scripts.
|
||||
* @return the script encoding
|
||||
*/
|
||||
public Charset getEncoding() {
|
||||
return this.encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encoding to use when reading the schema and data scripts.
|
||||
* @param encoding encoding to use when reading the schema and data scripts
|
||||
*/
|
||||
public void setEncoding(Charset encoding) {
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mode to use when determining whether database initialization should be
|
||||
* performed.
|
||||
* @return the initialization mode
|
||||
* @since 2.5.1
|
||||
*/
|
||||
public DatabaseInitializationMode getMode() {
|
||||
return this.mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mode the use when determining whether database initialization should be
|
||||
* performed.
|
||||
* @param mode the initialization mode
|
||||
* @since 2.5.1
|
||||
*/
|
||||
public void setMode(DatabaseInitializationMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Base class for {@link DatabaseInitializerDetector DatabaseInitializerDetectors} that
|
||||
* detect database initializer beans by type.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public abstract class AbstractBeansOfTypeDatabaseInitializerDetector implements DatabaseInitializerDetector {
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
Set<Class<?>> types = getDatabaseInitializerBeanTypes();
|
||||
return new BeansOfTypeDetector(types).detect(beanFactory);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bean types that should be detected as being database initializers.
|
||||
* @return the database initializer bean types
|
||||
*/
|
||||
protected abstract Set<Class<?>> getDatabaseInitializerBeanTypes();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Base class for {@link DependsOnDatabaseInitializationDetector
|
||||
* DependsOnDatabaseInitializationDetectors} that detect by type beans that depend upon
|
||||
* database initialization.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public abstract class AbstractBeansOfTypeDependsOnDatabaseInitializationDetector
|
||||
implements DependsOnDatabaseInitializationDetector {
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
Set<Class<?>> types = getDependsOnDatabaseInitializationBeanTypes();
|
||||
return new BeansOfTypeDetector(types).detect(beanFactory);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bean types that should be detected as depending on database
|
||||
* initialization.
|
||||
* @return the database initialization dependent bean types
|
||||
*/
|
||||
protected abstract Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes();
|
||||
|
||||
}
|
||||
@@ -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.dependency;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
/**
|
||||
* {@link DependsOnDatabaseInitializationDetector} that detects beans annotated with
|
||||
* {@link DependsOnDatabaseInitialization}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class AnnotationDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
Set<String> dependentBeans = new HashSet<>();
|
||||
for (String beanName : beanFactory.getBeanDefinitionNames()) {
|
||||
if (beanFactory.findAnnotationOnBean(beanName, DependsOnDatabaseInitialization.class, false) != null) {
|
||||
dependentBeans.add(beanName);
|
||||
}
|
||||
}
|
||||
return dependentBeans;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Helper class for detecting beans of particular types in a bean factory.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class BeansOfTypeDetector {
|
||||
|
||||
private final Set<Class<?>> types;
|
||||
|
||||
BeansOfTypeDetector(Set<Class<?>> types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
Set<String> detect(ListableBeanFactory beanFactory) {
|
||||
Set<String> beanNames = new HashSet<>();
|
||||
for (Class<?> type : this.types) {
|
||||
try {
|
||||
String[] names = beanFactory.getBeanNamesForType(type, true, false);
|
||||
Arrays.stream(names).map(BeanFactoryUtils::transformedBeanName).forEach(beanNames::add);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
// Continue
|
||||
}
|
||||
}
|
||||
return beanNames;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.dependency;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.aot.AotDetector;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.EnvironmentAware;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolver;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configures beans that depend upon SQL database initialization with
|
||||
* {@link BeanDefinition#getDependsOn() dependencies} upon beans that perform database
|
||||
* initialization. Intended for {@link Import import} in configuration classes that define
|
||||
* database initialization beans or that define beans that require database initialization
|
||||
* to have completed before they are initialized.
|
||||
* <p>
|
||||
* Beans that initialize a database are identified by {@link DatabaseInitializerDetector
|
||||
* DatabaseInitializerDetectors}. Beans that depend upon database initialization are
|
||||
* identified by {@link DependsOnDatabaseInitializationDetector
|
||||
* DependsOnDatabaseInitializationDetectors}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
* @see DatabaseInitializerDetector
|
||||
* @see DependsOnDatabaseInitializationDetector
|
||||
* @see DependsOnDatabaseInitialization
|
||||
*/
|
||||
public class DatabaseInitializationDependencyConfigurer implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
|
||||
String name = DependsOnDatabaseInitializationPostProcessor.class.getName();
|
||||
if (!registry.containsBeanDefinition(name)) {
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(DependsOnDatabaseInitializationPostProcessor.class);
|
||||
registry.registerBeanDefinition(name, builder.getBeanDefinition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link BeanFactoryPostProcessor} used to configure database initialization
|
||||
* dependency relationships.
|
||||
*/
|
||||
static class DependsOnDatabaseInitializationPostProcessor
|
||||
implements BeanFactoryPostProcessor, EnvironmentAware, Ordered {
|
||||
|
||||
private Environment environment;
|
||||
|
||||
@Override
|
||||
public void setEnvironment(Environment environment) {
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
|
||||
if (AotDetector.useGeneratedArtifacts()) {
|
||||
return;
|
||||
}
|
||||
InitializerBeanNames initializerBeanNames = detectInitializerBeanNames(beanFactory);
|
||||
if (initializerBeanNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> previousInitializerBeanNamesBatch = null;
|
||||
for (Set<String> initializerBeanNamesBatch : initializerBeanNames.batchedBeanNames()) {
|
||||
for (String initializerBeanName : initializerBeanNamesBatch) {
|
||||
BeanDefinition beanDefinition = getBeanDefinition(initializerBeanName, beanFactory);
|
||||
beanDefinition
|
||||
.setDependsOn(merge(beanDefinition.getDependsOn(), previousInitializerBeanNamesBatch));
|
||||
}
|
||||
previousInitializerBeanNamesBatch = initializerBeanNamesBatch;
|
||||
}
|
||||
for (String dependsOnInitializationBeanNames : detectDependsOnInitializationBeanNames(beanFactory)) {
|
||||
BeanDefinition beanDefinition = getBeanDefinition(dependsOnInitializationBeanNames, beanFactory);
|
||||
beanDefinition.setDependsOn(merge(beanDefinition.getDependsOn(), initializerBeanNames.beanNames()));
|
||||
}
|
||||
}
|
||||
|
||||
private String[] merge(String[] source, Set<String> additional) {
|
||||
if (CollectionUtils.isEmpty(additional)) {
|
||||
return source;
|
||||
}
|
||||
Set<String> result = new LinkedHashSet<>((source != null) ? Arrays.asList(source) : Collections.emptySet());
|
||||
result.addAll(additional);
|
||||
return StringUtils.toStringArray(result);
|
||||
}
|
||||
|
||||
private InitializerBeanNames detectInitializerBeanNames(ConfigurableListableBeanFactory beanFactory) {
|
||||
List<DatabaseInitializerDetector> detectors = getDetectors(beanFactory, DatabaseInitializerDetector.class);
|
||||
InitializerBeanNames initializerBeanNames = new InitializerBeanNames();
|
||||
for (DatabaseInitializerDetector detector : detectors) {
|
||||
for (String beanName : detector.detect(beanFactory)) {
|
||||
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
|
||||
beanDefinition.setAttribute(DatabaseInitializerDetector.class.getName(),
|
||||
detector.getClass().getName());
|
||||
initializerBeanNames.detected(detector, beanName);
|
||||
}
|
||||
}
|
||||
for (DatabaseInitializerDetector detector : detectors) {
|
||||
detector.detectionComplete(beanFactory, initializerBeanNames.beanNames());
|
||||
}
|
||||
return initializerBeanNames;
|
||||
}
|
||||
|
||||
private Collection<String> detectDependsOnInitializationBeanNames(ConfigurableListableBeanFactory beanFactory) {
|
||||
List<DependsOnDatabaseInitializationDetector> detectors = getDetectors(beanFactory,
|
||||
DependsOnDatabaseInitializationDetector.class);
|
||||
Set<String> beanNames = new HashSet<>();
|
||||
for (DependsOnDatabaseInitializationDetector detector : detectors) {
|
||||
beanNames.addAll(detector.detect(beanFactory));
|
||||
}
|
||||
return beanNames;
|
||||
}
|
||||
|
||||
private <T> List<T> getDetectors(ConfigurableListableBeanFactory beanFactory, Class<T> type) {
|
||||
ArgumentResolver argumentResolver = ArgumentResolver.of(Environment.class, this.environment);
|
||||
return SpringFactoriesLoader.forDefaultResourceLocation(beanFactory.getBeanClassLoader())
|
||||
.load(type, argumentResolver);
|
||||
}
|
||||
|
||||
private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) {
|
||||
try {
|
||||
return beanFactory.getBeanDefinition(beanName);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException ex) {
|
||||
BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
|
||||
if (parentBeanFactory instanceof ConfigurableListableBeanFactory configurableBeanFactory) {
|
||||
return getBeanDefinition(beanName, configurableBeanFactory);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
static class InitializerBeanNames {
|
||||
|
||||
private final Map<DatabaseInitializerDetector, Set<String>> byDetectorBeanNames = new LinkedHashMap<>();
|
||||
|
||||
private final Set<String> beanNames = new LinkedHashSet<>();
|
||||
|
||||
private void detected(DatabaseInitializerDetector detector, String beanName) {
|
||||
this.byDetectorBeanNames.computeIfAbsent(detector, (key) -> new LinkedHashSet<>()).add(beanName);
|
||||
this.beanNames.add(beanName);
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return this.beanNames.isEmpty();
|
||||
}
|
||||
|
||||
private Iterable<Set<String>> batchedBeanNames() {
|
||||
return this.byDetectorBeanNames.values();
|
||||
}
|
||||
|
||||
private Set<String> beanNames() {
|
||||
return Collections.unmodifiableSet(this.beanNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* Detects beans that initialize an SQL database. Implementations should be registered in
|
||||
* {@code META-INF/spring.factories} under the key
|
||||
* {@code org.springframework.boot.sql.init.dependency.DatabaseInitializerDetector}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public interface DatabaseInitializerDetector extends Ordered {
|
||||
|
||||
/**
|
||||
* Detect beans defined in the given {@code beanFactory} that initialize a
|
||||
* {@link DataSource}.
|
||||
* @param beanFactory bean factory to examine
|
||||
* @return names of the detected {@code DataSource} initializer beans, or an empty set
|
||||
* if none were detected.
|
||||
*/
|
||||
Set<String> detect(ConfigurableListableBeanFactory beanFactory);
|
||||
|
||||
/**
|
||||
* Callback indicating that all known {@code DataSourceInitializerDetectors} have been
|
||||
* called and detection of beans that initialize a {@link DataSource} is complete.
|
||||
* @param beanFactory bean factory that was examined
|
||||
* @param dataSourceInitializerNames names of the {@code DataSource} initializer beans
|
||||
* detected by all known detectors
|
||||
*/
|
||||
default void detectionComplete(ConfigurableListableBeanFactory beanFactory,
|
||||
Set<String> dataSourceInitializerNames) {
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getOrder() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Indicate that a bean's creation and initialization depends upon database initialization
|
||||
* having completed. May be used on a bean's class or its {@link Bean @Bean} definition.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface DependsOnDatabaseInitialization {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.dependency;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
/**
|
||||
* Detects beans that depend on database initialization. Implementations should be
|
||||
* registered in {@code META-INF/spring.factories} under the key
|
||||
* {@code org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public interface DependsOnDatabaseInitializationDetector {
|
||||
|
||||
/**
|
||||
* Detect beans defined in the given {@code beanFactory} that depend on database
|
||||
* initialization. If no beans are detected, an empty set is returned.
|
||||
* @param beanFactory bean factory to examine
|
||||
* @return names of any beans that depend upon database initialization
|
||||
*/
|
||||
Set<String> detect(ConfigurableListableBeanFactory beanFactory);
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Infrastructure for establishing database initialization bean dependencies.
|
||||
*/
|
||||
package org.springframework.boot.sql.init.dependency;
|
||||
@@ -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 initialization of an SQL database.
|
||||
*/
|
||||
package org.springframework.boot.sql.init;
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.sql.init.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether basic script-based initialization of an SQL database is enabled.",
|
||||
"defaultValue": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.sql.init.mode",
|
||||
"level": "warning"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Depends On Database Initialization Detectors
|
||||
org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector=\
|
||||
org.springframework.boot.sql.init.dependency.AnnotationDependsOnDatabaseInitializationDetector
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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.autoconfigure.init;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link OnDatabaseInitializationCondition}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class OnDatabaseInitializationConditionTests {
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithPropertyNoSetMatches() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.another", "noise")), null);
|
||||
assertThat(outcome.isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithPropertySetToAlwaysMatches() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-mode=always")), null);
|
||||
assertThat(outcome.isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithPropertySetToEmbeddedMatches() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-mode=embedded")), null);
|
||||
assertThat(outcome.isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithPropertySetToNeverDoesNotMatch() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-mode=never")), null);
|
||||
assertThat(outcome.isMatch()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithPropertySetToEmptyStringIsIgnored() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-mode")), null);
|
||||
assertThat(outcome.isMatch()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeWithMultiplePropertiesUsesFirstSet() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode",
|
||||
"test.schema-mode", "test.init-schema-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-schema-mode=embedded")), null);
|
||||
assertThat(outcome.isMatch()).isTrue();
|
||||
assertThat(outcome.getMessage()).isEqualTo("TestDatabase Initialization test.init-schema-mode is EMBEDDED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeHasDedicatedDescription() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition
|
||||
.getMatchOutcome(mockConditionContext(TestPropertyValues.of("test.init-mode=embedded")), null);
|
||||
assertThat(outcome.getMessage()).isEqualTo("TestDatabase Initialization test.init-mode is EMBEDDED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void getMatchOutcomeHasWhenPropertyIsNotSetHasDefaultDescription() {
|
||||
OnDatabaseInitializationCondition condition = new OnTestDatabaseInitializationCondition("test.init-mode");
|
||||
ConditionOutcome outcome = condition.getMatchOutcome(mockConditionContext(TestPropertyValues.empty()), null);
|
||||
assertThat(outcome.getMessage()).isEqualTo("TestDatabase Initialization default value is EMBEDDED");
|
||||
}
|
||||
|
||||
private ConditionContext mockConditionContext(TestPropertyValues propertyValues) {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
propertyValues.applyTo(environment);
|
||||
ConditionContext conditionContext = mock(ConditionContext.class);
|
||||
given(conditionContext.getEnvironment()).willReturn(environment);
|
||||
return conditionContext;
|
||||
}
|
||||
|
||||
static class OnTestDatabaseInitializationCondition extends OnDatabaseInitializationCondition {
|
||||
|
||||
OnTestDatabaseInitializationCondition(String... properties) {
|
||||
super("Test", properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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.autoconfigure.init;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.aot.hint.RuntimeHints;
|
||||
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link SqlInitializationScriptsRuntimeHints}.
|
||||
*
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
class SqlInitializationScriptsRuntimeHintsTests {
|
||||
|
||||
@Test
|
||||
void shouldRegisterSchemaHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new SqlInitializationScriptsRuntimeHints().registerHints(hints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("schema.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("schema-all.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("schema-mysql.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("schema-postgres.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("schema-oracle.sql")).accepts(hints);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRegisterDataHints() {
|
||||
RuntimeHints hints = new RuntimeHints();
|
||||
new SqlInitializationScriptsRuntimeHints().registerHints(hints, getClass().getClassLoader());
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("data.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("data-all.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("data-mysql.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("data-postgres.sql")).accepts(hints);
|
||||
assertThat(RuntimeHintsPredicates.resource().forResource("data-oracle.sql")).accepts(hints);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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.dependency;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.then;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.reset;
|
||||
|
||||
/**
|
||||
* Tests for {@link DatabaseInitializationDependencyConfigurer}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
class DatabaseInitializationDependencyConfigurerTests {
|
||||
|
||||
private final ConfigurableEnvironment environment = new MockEnvironment();
|
||||
|
||||
@TempDir
|
||||
File temp;
|
||||
|
||||
@BeforeEach
|
||||
void resetMocks() {
|
||||
reset(MockDatabaseInitializerDetector.instance, OrderedNearLowestMockDatabaseInitializerDetector.instance,
|
||||
OrderedLowestMockDatabaseInitializerDetector.instance,
|
||||
MockedDependsOnDatabaseInitializationDetector.instance);
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanFactoryPostProcessorHasOrderAllowingSubsequentPostProcessorsToFineTuneDependencies() {
|
||||
performDetection(Arrays.asList(MockDatabaseInitializerDetector.class,
|
||||
MockedDependsOnDatabaseInitializationDetector.class), (context) -> {
|
||||
BeanDefinition alpha = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition bravo = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
context.register(DependsOnCaptor.class);
|
||||
context.register(DependencyConfigurerConfiguration.class);
|
||||
context.registerBeanDefinition("alpha", alpha);
|
||||
context.registerBeanDefinition("bravo", bravo);
|
||||
given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("alpha"));
|
||||
given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("bravo"));
|
||||
context.refresh();
|
||||
assertThat(DependsOnCaptor.dependsOn).hasEntrySatisfying("bravo",
|
||||
(dependencies) -> assertThat(dependencies).containsExactly("alpha"));
|
||||
assertThat(DependsOnCaptor.dependsOn).doesNotContainKey("alpha");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() {
|
||||
performDetection(Arrays.asList(ConstructorInjectionDatabaseInitializerDetector.class,
|
||||
ConstructorInjectionDependsOnDatabaseInitializationDetector.class), (context) -> {
|
||||
BeanDefinition alpha = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
context.registerBeanDefinition("alpha", alpha);
|
||||
context.register(DependencyConfigurerConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(ConstructorInjectionDatabaseInitializerDetector.environment).isEqualTo(this.environment);
|
||||
assertThat(ConstructorInjectionDependsOnDatabaseInitializationDetector.environment)
|
||||
.isEqualTo(this.environment);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDependenciesAreConfiguredThenBeansThatDependUponDatabaseInitializationDependUponDetectedDatabaseInitializers() {
|
||||
BeanDefinition alpha = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition bravo = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
performDetection(Arrays.asList(MockDatabaseInitializerDetector.class,
|
||||
MockedDependsOnDatabaseInitializationDetector.class), (context) -> {
|
||||
context.registerBeanDefinition("alpha", alpha);
|
||||
context.registerBeanDefinition("bravo", bravo);
|
||||
given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("alpha"));
|
||||
given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("bravo"));
|
||||
context.register(DependencyConfigurerConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(alpha.getAttribute(DatabaseInitializerDetector.class.getName()))
|
||||
.isEqualTo(MockDatabaseInitializerDetector.class.getName());
|
||||
assertThat(bravo.getAttribute(DatabaseInitializerDetector.class.getName())).isNull();
|
||||
then(MockDatabaseInitializerDetector.instance).should()
|
||||
.detectionComplete(context.getBeanFactory(), Collections.singleton("alpha"));
|
||||
assertThat(bravo.getDependsOn()).containsExactly("alpha");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDependenciesAreConfiguredDetectedDatabaseInitializersAreInitializedInCorrectOrder() {
|
||||
BeanDefinition alpha = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition bravo1 = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition bravo2 = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition charlie = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition delta = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
performDetection(
|
||||
Arrays.asList(MockDatabaseInitializerDetector.class, OrderedLowestMockDatabaseInitializerDetector.class,
|
||||
OrderedNearLowestMockDatabaseInitializerDetector.class,
|
||||
MockedDependsOnDatabaseInitializationDetector.class),
|
||||
(context) -> {
|
||||
given(MockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("alpha"));
|
||||
given(OrderedNearLowestMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(new LinkedHashSet<>(Arrays.asList("bravo1", "bravo2")));
|
||||
given(OrderedLowestMockDatabaseInitializerDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(new LinkedHashSet<>(Arrays.asList("charlie")));
|
||||
given(MockedDependsOnDatabaseInitializationDetector.instance.detect(context.getBeanFactory()))
|
||||
.willReturn(Collections.singleton("delta"));
|
||||
context.registerBeanDefinition("alpha", alpha);
|
||||
context.registerBeanDefinition("bravo1", bravo1);
|
||||
context.registerBeanDefinition("bravo2", bravo2);
|
||||
context.registerBeanDefinition("charlie", charlie);
|
||||
context.registerBeanDefinition("delta", delta);
|
||||
context.register(DependencyConfigurerConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(delta.getDependsOn()).containsExactlyInAnyOrder("alpha", "bravo1", "bravo2", "charlie");
|
||||
assertThat(charlie.getDependsOn()).containsExactly("bravo1", "bravo2");
|
||||
assertThat(bravo1.getDependsOn()).containsExactly("alpha");
|
||||
assertThat(bravo2.getDependsOn()).containsExactly("alpha");
|
||||
assertThat(alpha.getDependsOn()).isNullOrEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenInAnAotProcessedContextDependsOnDatabaseInitializationPostProcessorDoesNothing() {
|
||||
withAotEnabled(() -> {
|
||||
BeanDefinition alpha = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
BeanDefinition bravo = BeanDefinitionBuilder.rootBeanDefinition(String.class).getBeanDefinition();
|
||||
performDetection(Arrays.asList(MockDatabaseInitializerDetector.class,
|
||||
MockedDependsOnDatabaseInitializationDetector.class), (context) -> {
|
||||
context.registerBeanDefinition("alpha", alpha);
|
||||
context.registerBeanDefinition("bravo", bravo);
|
||||
context.register(DependencyConfigurerConfiguration.class);
|
||||
context.refresh();
|
||||
assertThat(alpha.getAttribute(DatabaseInitializerDetector.class.getName())).isNull();
|
||||
assertThat(bravo.getAttribute(DatabaseInitializerDetector.class.getName())).isNull();
|
||||
then(MockDatabaseInitializerDetector.instance).shouldHaveNoInteractions();
|
||||
assertThat(bravo.getDependsOn()).isNull();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void withAotEnabled(Runnable action) {
|
||||
System.setProperty("spring.aot.enabled", "true");
|
||||
try {
|
||||
action.run();
|
||||
}
|
||||
finally {
|
||||
System.clearProperty("spring.aot.enabled");
|
||||
}
|
||||
}
|
||||
|
||||
private void performDetection(Collection<Class<?>> detectors,
|
||||
Consumer<AnnotationConfigApplicationContext> contextCallback) {
|
||||
DetectorSpringFactoriesClassLoader detectorSpringFactories = new DetectorSpringFactoriesClassLoader(this.temp);
|
||||
detectors.forEach(detectorSpringFactories::register);
|
||||
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
|
||||
context.setEnvironment(this.environment);
|
||||
context.setClassLoader(detectorSpringFactories);
|
||||
contextCallback.accept(context);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import(DatabaseInitializationDependencyConfigurer.class)
|
||||
static class DependencyConfigurerConfiguration {
|
||||
|
||||
}
|
||||
|
||||
static class ConstructorInjectionDatabaseInitializerDetector implements DatabaseInitializerDetector {
|
||||
|
||||
private static Environment environment;
|
||||
|
||||
ConstructorInjectionDatabaseInitializerDetector(Environment environment) {
|
||||
ConstructorInjectionDatabaseInitializerDetector.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return Collections.singleton("alpha");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ConstructorInjectionDependsOnDatabaseInitializationDetector
|
||||
implements DependsOnDatabaseInitializationDetector {
|
||||
|
||||
private static Environment environment;
|
||||
|
||||
ConstructorInjectionDependsOnDatabaseInitializationDetector(Environment environment) {
|
||||
ConstructorInjectionDependsOnDatabaseInitializationDetector.environment = environment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MockDatabaseInitializerDetector implements DatabaseInitializerDetector {
|
||||
|
||||
private static final DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return instance.detect(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detectionComplete(ConfigurableListableBeanFactory beanFactory,
|
||||
Set<String> databaseInitializerNames) {
|
||||
instance.detectionComplete(beanFactory, databaseInitializerNames);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class OrderedLowestMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
|
||||
|
||||
private static final DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return instance.detect(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class OrderedNearLowestMockDatabaseInitializerDetector implements DatabaseInitializerDetector {
|
||||
|
||||
private static final DatabaseInitializerDetector instance = mock(DatabaseInitializerDetector.class);
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return instance.detect(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.LOWEST_PRECEDENCE - 100;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MockedDependsOnDatabaseInitializationDetector implements DependsOnDatabaseInitializationDetector {
|
||||
|
||||
private static final DependsOnDatabaseInitializationDetector instance = mock(
|
||||
DependsOnDatabaseInitializationDetector.class);
|
||||
|
||||
@Override
|
||||
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
|
||||
return instance.detect(beanFactory);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DetectorSpringFactoriesClassLoader extends ClassLoader {
|
||||
|
||||
private final Set<Class<DatabaseInitializerDetector>> databaseInitializerDetectors = new HashSet<>();
|
||||
|
||||
private final Set<Class<DependsOnDatabaseInitializationDetector>> dependsOnDatabaseInitializationDetectors = new HashSet<>();
|
||||
|
||||
private final File temp;
|
||||
|
||||
DetectorSpringFactoriesClassLoader(File temp) {
|
||||
this.temp = temp;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void register(Class<?> detector) {
|
||||
if (DatabaseInitializerDetector.class.isAssignableFrom(detector)) {
|
||||
this.databaseInitializerDetectors.add((Class<DatabaseInitializerDetector>) detector);
|
||||
}
|
||||
else if (DependsOnDatabaseInitializationDetector.class.isAssignableFrom(detector)) {
|
||||
this.dependsOnDatabaseInitializationDetectors
|
||||
.add((Class<DependsOnDatabaseInitializationDetector>) detector);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported detector type '" + detector.getName() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<URL> getResources(String name) throws IOException {
|
||||
if (!"META-INF/spring.factories".equals(name)) {
|
||||
return super.findResources(name);
|
||||
}
|
||||
Properties properties = new Properties();
|
||||
properties.put(DatabaseInitializerDetector.class.getName(),
|
||||
String.join(",", this.databaseInitializerDetectors.stream().map(Class::getName).toList()));
|
||||
properties.put(DependsOnDatabaseInitializationDetector.class.getName(), String.join(",",
|
||||
this.dependsOnDatabaseInitializationDetectors.stream().map(Class::getName).toList()));
|
||||
File springFactories = new File(this.temp, "spring.factories");
|
||||
try (FileWriter writer = new FileWriter(springFactories)) {
|
||||
properties.store(writer, "");
|
||||
}
|
||||
return Collections.enumeration(Collections.singleton(springFactories.toURI().toURL()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class DependsOnCaptor {
|
||||
|
||||
static final Map<String, List<String>> dependsOn = new HashMap<>();
|
||||
|
||||
@Bean
|
||||
static BeanFactoryPostProcessor dependsOnCapturingPostProcessor() {
|
||||
return (beanFactory) -> {
|
||||
dependsOn.clear();
|
||||
for (String name : beanFactory.getBeanDefinitionNames()) {
|
||||
storeDependsOn(name, beanFactory);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void storeDependsOn(String name, ConfigurableListableBeanFactory beanFactory) {
|
||||
String[] dependsOn = beanFactory.getBeanDefinition(name).getDependsOn();
|
||||
if (dependsOn != null) {
|
||||
DependsOnCaptor.dependsOn.put(name, Arrays.asList(dependsOn));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright 2012-2025 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;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.testsupport.classpath.resources.WithResource;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Base class for testing {@link AbstractScriptDatabaseInitializer} implementations.
|
||||
*
|
||||
* @param <T> type of the initializer being tested
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class AbstractScriptDatabaseInitializerTests<T extends AbstractScriptDatabaseInitializer> {
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenDatabaseIsInitializedThenSchemaAndDataScriptsAreApplied() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDatabaseIsInitializedWithDirectoryLocationsThenFailureIsHelpful() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("/org/springframework/boot/sql/init"));
|
||||
settings.setDataLocations(Arrays.asList("/org/springframework/boot/sql/init"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
|
||||
.withMessage("No schema scripts found at location '/org/springframework/boot/sql/init'");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithDataSqlResource
|
||||
void whenContinueOnErrorIsFalseThenInitializationFailsOnError() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThatExceptionOfType(DataAccessException.class).isThrownBy(initializer::initializeDatabase);
|
||||
assertThatDatabaseWasAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithDataSqlResource
|
||||
void whenContinueOnErrorIsTrueThenInitializationDoesNotFailOnError() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setContinueOnError(true);
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThatDatabaseWasAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoScriptsExistAtASchemaLocationThenInitializationFails() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("does-not-exist.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
|
||||
.withMessage("No schema scripts found at location 'does-not-exist.sql'");
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoScriptsExistAtADataLocationThenInitializationFails() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setDataLocations(Arrays.asList("does-not-exist.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThatIllegalStateException().isThrownBy(initializer::initializeDatabase)
|
||||
.withMessage("No data scripts found at location 'does-not-exist.sql'");
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoScriptsExistAtAnOptionalSchemaLocationThenDatabaseIsNotAccessed() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("optional:does-not-exist.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isFalse();
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenNoScriptsExistAtAnOptionalDataLocationThenDatabaseIsNotAccessed() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setDataLocations(Arrays.asList("optional:does-not-exist.sql"));
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isFalse();
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsNeverThenEmbeddedDatabaseIsNotInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.NEVER);
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isFalse();
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsNeverThenStandaloneDatabaseIsNotInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.NEVER);
|
||||
T initializer = createStandaloneDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isFalse();
|
||||
assertThatDatabaseWasNotAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsEmbeddedThenEmbeddedDatabaseIsInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.EMBEDDED);
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsEmbeddedThenStandaloneDatabaseIsNotInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.EMBEDDED);
|
||||
T initializer = createStandaloneDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isFalse();
|
||||
assertThatDatabaseWasAccessed(initializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsAlwaysThenEmbeddedDatabaseIsInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.ALWAYS);
|
||||
T initializer = createEmbeddedDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThat(numberOfEmbeddedRows("SELECT COUNT(*) FROM EXAMPLE")).isOne();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithSchemaSqlResource
|
||||
@WithDataSqlResource
|
||||
void whenModeIsAlwaysThenStandaloneDatabaseIsInitialized() {
|
||||
DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
|
||||
settings.setSchemaLocations(Arrays.asList("schema.sql"));
|
||||
settings.setDataLocations(Arrays.asList("data.sql"));
|
||||
settings.setMode(DatabaseInitializationMode.ALWAYS);
|
||||
T initializer = createStandaloneDatabaseInitializer(settings);
|
||||
assertThat(initializer.initializeDatabase()).isTrue();
|
||||
assertThat(numberOfStandaloneRows("SELECT COUNT(*) FROM EXAMPLE")).isOne();
|
||||
}
|
||||
|
||||
protected abstract T createStandaloneDatabaseInitializer(DatabaseInitializationSettings settings);
|
||||
|
||||
protected abstract T createEmbeddedDatabaseInitializer(DatabaseInitializationSettings settings);
|
||||
|
||||
protected abstract int numberOfEmbeddedRows(String sql);
|
||||
|
||||
protected abstract int numberOfStandaloneRows(String sql);
|
||||
|
||||
private void assertThatDatabaseWasAccessed(T initializer) {
|
||||
assertDatabaseAccessed(true, initializer);
|
||||
}
|
||||
|
||||
private void assertThatDatabaseWasNotAccessed(T initializer) {
|
||||
assertDatabaseAccessed(false, initializer);
|
||||
}
|
||||
|
||||
protected abstract void assertDatabaseAccessed(boolean accessed, T initializer);
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@WithResource(name = "schema.sql", content = """
|
||||
CREATE TABLE EXAMPLE (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(30)
|
||||
);
|
||||
""")
|
||||
protected @interface WithSchemaSqlResource {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@WithResource(name = "data.sql", content = "INSERT INTO EXAMPLE VALUES (1, 'Andy');")
|
||||
protected @interface WithDataSqlResource {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user