Provide more pluggable way to indicate DataSource init dependencies

Closes gh-17619
Closes gh-25559
pull/25562/head
Andy Wilkinson 4 years ago
parent 99b7d29c2c
commit ed72bca6c8

@ -26,7 +26,6 @@ import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.flywaydb.core.Flyway;
@ -34,7 +33,6 @@ import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.flywaydb.core.api.migration.JavaMigration;
import org.jooq.DSLContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -45,22 +43,15 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDslContextDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayEntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayNamedParameterJdbcOperationsDependencyConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jooq.DslContextDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@ -68,12 +59,8 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -100,8 +87,7 @@ import org.springframework.util.StringUtils;
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
@Import({ FlywayEntityManagerFactoryDependsOnPostProcessor.class, FlywayJdbcOperationsDependsOnPostProcessor.class,
FlywayNamedParameterJdbcOperationsDependencyConfiguration.class, FlywayDslContextDependsOnPostProcessor.class })
@Import(DataSourceInitializationDependencyConfigurer.class)
public class FlywayAutoConfiguration {
@Bean
@ -118,10 +104,6 @@ public class FlywayAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(Flyway.class)
@EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
@Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class,
FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor.class,
FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor.class,
FlywayMigrationInitializerDslContextDependsOnPostProcessor.class })
public static class FlywayConfiguration {
@Bean
@ -325,122 +307,6 @@ public class FlywayAutoConfiguration {
}
/**
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
* {@link FlywayMigrationInitializer} beans.
*/
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
/**
* Post processor to ensure that {@link JdbcOperations} beans depend on any
* {@link FlywayMigrationInitializer} beans.
*/
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
static class FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor
extends JdbcOperationsDependsOnPostProcessor {
FlywayMigrationInitializerJdbcOperationsDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
/**
* Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on
* any {@link FlywayMigrationInitializer} beans.
*/
@ConditionalOnClass(NamedParameterJdbcOperations.class)
@ConditionalOnBean(NamedParameterJdbcOperations.class)
static class FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
FlywayMigrationInitializerNamedParameterJdbcOperationsDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
/**
* Post processor to ensure that {@link DSLContext} beans depend on any
* {@link FlywayMigrationInitializer} beans.
*/
@ConditionalOnClass(DSLContext.class)
@ConditionalOnBean(DSLContext.class)
static class FlywayMigrationInitializerDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor {
FlywayMigrationInitializerDslContextDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
/**
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
* {@link Flyway} beans.
*/
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class FlywayEntityManagerFactoryDependsOnPostProcessor extends EntityManagerFactoryDependsOnPostProcessor {
FlywayEntityManagerFactoryDependsOnPostProcessor() {
super(Flyway.class);
}
}
/**
* Post processor to ensure that {@link JdbcOperations} beans depend on any
* {@link Flyway} beans.
*/
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
static class FlywayJdbcOperationsDependsOnPostProcessor extends JdbcOperationsDependsOnPostProcessor {
FlywayJdbcOperationsDependsOnPostProcessor() {
super(Flyway.class);
}
}
/**
* Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on
* any {@link Flyway} beans.
*/
@ConditionalOnClass(NamedParameterJdbcOperations.class)
@ConditionalOnBean(NamedParameterJdbcOperations.class)
protected static class FlywayNamedParameterJdbcOperationsDependencyConfiguration
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
public FlywayNamedParameterJdbcOperationsDependencyConfiguration() {
super(Flyway.class);
}
}
/**
* Post processor to ensure that {@link DSLContext} beans depend on any {@link Flyway}
* beans.
*/
@ConditionalOnClass(DSLContext.class)
@ConditionalOnBean(DSLContext.class)
protected static class FlywayDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor {
public FlywayDslContextDependsOnPostProcessor() {
super(Flyway.class);
}
}
private static class LocationResolver {
private static final String VENDOR_PLACEHOLDER = "{vendor}";

@ -0,0 +1,37 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.flyway;
import java.util.Collections;
import java.util.Set;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector;
import org.springframework.boot.jdbc.init.DataSourceInitializerDetector;
/**
* A {@link DataSourceInitializerDetector} for {@link FlywayMigrationInitializer}.
*
* @author Andy Wilkinson
*/
class FlywayMigrationInitializerDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
@Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
return Collections.singleton(FlywayMigrationInitializer.class);
}
}

@ -16,19 +16,13 @@
package org.springframework.boot.autoconfigure.jdbc;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
/**
* Configuration for {@link DataSource} initialization using DDL and DML scripts.
@ -37,63 +31,12 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnSingleCandidate(DataSource.class)
@Import(DataSourceInitializationDependencyConfigurer.class)
class DataSourceInitializationConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.datasource", name = "initialization-order", havingValue = "before-jpa",
matchIfMissing = true)
@Import({ DataSourceInitializationJdbcOperationsDependsOnPostProcessor.class,
DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor.class,
DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor.class })
static class BeforeJpaDataSourceInitializationConfiguration {
@Bean
DataSourceInitialization dataSourceInitialization(DataSource dataSource, DataSourceProperties properties) {
return new DataSourceInitialization(dataSource, properties);
}
}
/**
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
* {@link DataSourceInitialization} beans.
*/
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
static class DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor() {
super(DataSourceInitialization.class);
}
}
/**
* Post processor to ensure that {@link JdbcOperations} beans depend on any
* {@link DataSourceInitialization} beans.
*/
@ConditionalOnClass(JdbcOperations.class)
static class DataSourceInitializationJdbcOperationsDependsOnPostProcessor
extends JdbcOperationsDependsOnPostProcessor {
DataSourceInitializationJdbcOperationsDependsOnPostProcessor() {
super(DataSourceInitialization.class);
}
}
/**
* Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on
* any {@link DataSourceInitialization} beans.
*/
@ConditionalOnClass(NamedParameterJdbcOperations.class)
protected static class DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
public DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor() {
super(DataSourceInitialization.class);
}
}
}

@ -0,0 +1,37 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.jdbc;
import java.util.Collections;
import java.util.Set;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector;
import org.springframework.boot.jdbc.init.DataSourceInitializerDetector;
/**
* A {@link DataSourceInitializerDetector} for {@link DataSourceInitialization}.
*
* @author Andy Wilkinson
*/
class DataSourceInitializationDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
@Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
return Collections.singleton(DataSourceInitialization.class);
}
}

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.jdbc;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
import org.springframework.jdbc.core.JdbcOperations;
/**
@ -32,7 +33,9 @@ import org.springframework.jdbc.core.JdbcOperations;
* @author Andrii Hrytsiuk
* @since 2.0.4
* @see BeanDefinition#setDependsOn(String[])
* @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector}
*/
@Deprecated
public class JdbcOperationsDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
/**

@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
@ -43,7 +44,8 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
@Import({ DataSourceInitializationDependencyConfigurer.class, JdbcTemplateConfiguration.class,
NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* 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.
@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.jdbc;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
/**
@ -30,7 +31,9 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
* @author Andrii Hrytsiuk
* @since 2.1.4
* @see BeanDefinition#setDependsOn(String[])
* @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector}
*/
@Deprecated
public class NamedParameterJdbcOperationsDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
/**

@ -21,6 +21,7 @@ import org.jooq.DSLContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
/**
* {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all
@ -29,7 +30,9 @@ import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostPr
* @author Eddú Meléndez
* @since 2.3.9
* @see BeanDefinition#setDependsOn(String[])
* @deprecated since 2.5.0 in favor of {@link DependsOnDataSourceInitializationDetector}
*/
@Deprecated
public class DslContextDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
/**

@ -18,12 +18,10 @@ package org.springframework.boot.autoconfigure.liquibase;
import java.util.function.Supplier;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import liquibase.change.DatabaseChange;
import liquibase.integration.spring.SpringLiquibase;
import org.jooq.DSLContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -35,28 +33,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jooq.DslContextDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDslContextDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseEntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.StringUtils;
/**
@ -79,10 +66,7 @@ import org.springframework.util.StringUtils;
@ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
@Conditional(LiquibaseDataSourceCondition.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
@Import({ LiquibaseEntityManagerFactoryDependsOnPostProcessor.class,
LiquibaseJdbcOperationsDependsOnPostProcessor.class,
LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor.class,
LiquibaseDslContextDependsOnPostProcessor.class })
@Import(DataSourceInitializationDependencyConfigurer.class)
public class LiquibaseAutoConfiguration {
@Bean
@ -175,64 +159,6 @@ public class LiquibaseAutoConfiguration {
}
/**
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
* {@link SpringLiquibase} beans.
*/
@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class LiquibaseEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
LiquibaseEntityManagerFactoryDependsOnPostProcessor() {
super(SpringLiquibase.class);
}
}
/**
* Additional configuration to ensure that {@link JdbcOperations} beans depend on any
* {@link SpringLiquibase} beans.
*/
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
static class LiquibaseJdbcOperationsDependsOnPostProcessor extends JdbcOperationsDependsOnPostProcessor {
LiquibaseJdbcOperationsDependsOnPostProcessor() {
super(SpringLiquibase.class);
}
}
/**
* Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on
* any {@link SpringLiquibase} beans.
*/
@ConditionalOnClass(NamedParameterJdbcOperations.class)
@ConditionalOnBean(NamedParameterJdbcOperations.class)
static class LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
LiquibaseNamedParameterJdbcOperationsDependsOnPostProcessor() {
super(SpringLiquibase.class);
}
}
/**
* Post processor to ensure that {@link DSLContext} beans depend on any
* {@link SpringLiquibase} beans.
*/
@ConditionalOnClass(DSLContext.class)
@ConditionalOnBean(DSLContext.class)
static class LiquibaseDslContextDependsOnPostProcessor extends DslContextDependsOnPostProcessor {
LiquibaseDslContextDependsOnPostProcessor() {
super(SpringLiquibase.class);
}
}
static final class LiquibaseDataSourceCondition extends AnyNestedCondition {
LiquibaseDataSourceCondition() {

@ -17,23 +17,16 @@
package org.springframework.boot.autoconfigure.orm.jpa;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ResourceLoader;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
/**
@ -52,18 +45,4 @@ import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {
@ConditionalOnProperty(prefix = "spring.datasource", name = "initialization-order", havingValue = "after-jpa",
matchIfMissing = false)
static class AfterJpaDataSourceInitializationConfiguration {
@Bean
HibernatePropertiesCustomizer dataSourceInitializationCustomizer(DataSource dataSource,
DataSourceProperties properties, ResourceLoader resourceLoader) {
return (hibernateProperties) -> hibernateProperties.put(AvailableSettings.SCHEMA_MANAGEMENT_TOOL,
new SpringBootSchemaManagementTool(
new DataSourceInitializer(dataSource, properties, resourceLoader)));
}
}
}

@ -1,87 +0,0 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.orm.jpa;
import org.hibernate.boot.Metadata;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.resource.transaction.spi.DdlTransactionIsolator;
import org.hibernate.tool.schema.TargetType;
import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool;
import org.hibernate.tool.schema.internal.exec.GenerationTarget;
import org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase;
import org.hibernate.tool.schema.internal.exec.JdbcContext;
import org.hibernate.tool.schema.spi.ExecutionOptions;
import org.hibernate.tool.schema.spi.SchemaCreator;
import org.hibernate.tool.schema.spi.SourceDescriptor;
import org.hibernate.tool.schema.spi.TargetDescriptor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer;
/**
* Spring Boot {@link SchemaCreator}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class SpringBootSchemaCreator implements SchemaCreator {
private static final CoreMessageLogger log = CoreLogging.messageLogger(SpringBootSchemaCreator.class);
private final HibernateSchemaManagementTool tool;
private final DataSourceInitializer dataSourceInitializer;
private final SchemaCreator creator;
SpringBootSchemaCreator(HibernateSchemaManagementTool tool, SchemaCreator creator,
DataSourceInitializer dataSourceInitializer) {
this.tool = tool;
this.creator = creator;
this.dataSourceInitializer = dataSourceInitializer;
}
@Override
public void doCreation(Metadata metadata, ExecutionOptions options, SourceDescriptor sourceDescriptor,
TargetDescriptor targetDescriptor) {
if (!targetDescriptor.getTargetTypes().contains(TargetType.DATABASE)) {
this.creator.doCreation(metadata, options, sourceDescriptor, targetDescriptor);
return;
}
GenerationTarget databaseTarget = getDatabaseTarget(options, targetDescriptor);
databaseTarget.prepare();
try {
this.creator.doCreation(metadata, options, sourceDescriptor, targetDescriptor);
this.dataSourceInitializer.initializeDataSource();
}
finally {
try {
databaseTarget.release();
}
catch (Exception ex) {
log.debugf("Problem releasing GenerationTarget [%s] : %s", databaseTarget, ex.getMessage());
}
}
}
private GenerationTarget getDatabaseTarget(ExecutionOptions options, TargetDescriptor targetDescriptor) {
JdbcContext jdbcContext = this.tool.resolveJdbcContext(options.getConfigurationValues());
DdlTransactionIsolator ddlTransactionIsolator = this.tool.getDdlTransactionIsolator(jdbcContext);
return new GenerationTargetToDatabase(ddlTransactionIsolator);
}
}

@ -1,48 +0,0 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.orm.jpa;
import java.util.Map;
import org.hibernate.tool.schema.internal.HibernateSchemaManagementTool;
import org.hibernate.tool.schema.spi.SchemaCreator;
import org.hibernate.tool.schema.spi.SchemaManagementTool;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer;
/**
* Spring Boot {@link SchemaManagementTool}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class SpringBootSchemaManagementTool extends HibernateSchemaManagementTool {
private final DataSourceInitializer dataSourceInitializer;
SpringBootSchemaManagementTool(DataSourceInitializer dataSourceInitializer) {
this.dataSourceInitializer = dataSourceInitializer;
}
@Override
@SuppressWarnings("rawtypes")
public SchemaCreator getSchemaCreator(Map options) {
SchemaCreator creator = super.getSchemaCreator(options);
return new SpringBootSchemaCreator(this, creator, this.dataSourceInitializer);
}
}

@ -756,10 +756,6 @@
"name": "spring.datasource.initialization-mode",
"defaultValue": "embedded"
},
{
"name": "spring.datasource.initialization-order",
"defaultValue": "before-jpa"
},
{
"name": "spring.datasource.jmx-enabled",
"type": "java.lang.Boolean",
@ -1905,17 +1901,6 @@
}
]
},
{
"name": "spring.datasource.initialization-order",
"values": [
{
"value": "before-jpa"
},
{
"value": "after-jpa"
}
]
},
{
"name": "spring.datasource.schema",
"providers": [

@ -166,3 +166,9 @@ org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProv
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
# DataSource initializer detectors
org.springframework.boot.jdbc.init.DataSourceInitializerDetector=\
org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializerDataSourceInitializerDetector,\
org.springframework.boot.autoconfigure.jdbc.DataSourceInitializationDataSourceInitializerDetector

@ -577,7 +577,7 @@ class FlywayAutoConfigurationTests {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class)
.run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactly("flywayInitializer", "flyway");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayInitializer", "flyway");
});
}
@ -586,7 +586,8 @@ class FlywayAutoConfigurationTests {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class,
CustomFlywayMigrationInitializer.class).run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactly("flywayMigrationInitializer", "flyway");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("flywayMigrationInitializer",
"flyway");
});
}
@ -595,7 +596,7 @@ class FlywayAutoConfigurationTests {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, JooqConfiguration.class,
CustomFlyway.class).run((context) -> {
BeanDefinition beanDefinition = context.getBeanFactory().getBeanDefinition("dslContext");
assertThat(beanDefinition.getDependsOn()).containsExactly("customFlyway");
assertThat(beanDefinition.getDependsOn()).containsExactlyInAnyOrder("customFlyway");
});
}

@ -43,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitialization;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

@ -60,6 +60,7 @@ import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurat
import org.springframework.boot.autoconfigure.orm.jpa.mapping.NonAnnotatedEntity;
import org.springframework.boot.autoconfigure.orm.jpa.test.City;
import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitialization;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform;
@ -123,8 +124,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
contextRunner().withUserConfiguration(TestInitializedJpaConfiguration.class)
.withClassLoader(new HideDataScriptClassLoader())
.withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.hibernate.ddl-auto:create-drop",
"spring.datasource.data:classpath:/city.sql",
"spring.datasource.initialization-order=after-jpa")
"spring.datasource.data:classpath:/city.sql", "spring.jpa.defer-datasource-initialization=true")
.run((context) -> assertThat(context.getBean(TestInitializedJpaConfiguration.class).called).isTrue());
}
@ -287,7 +287,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
contextRunner().withClassLoader(new HideDataScriptClassLoader())
.withPropertyValues("spring.datasource.data:classpath:/db/non-annotated-data.sql",
"spring.jpa.mapping-resources=META-INF/mappings/non-annotated.xml",
"spring.datasource.initialization-order=after-jpa")
"spring.jpa.defer-datasource-initialization=true")
.run((context) -> {
EntityManager em = context.getBean(EntityManagerFactory.class).createEntityManager();
NonAnnotatedEntity found = em.find(NonAnnotatedEntity.class, 2000L);
@ -360,8 +360,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
.withUserConfiguration(TestInitializedJpaConfiguration.class)
.withClassLoader(new HideDataScriptClassLoader())
.withPropertyValues("spring.jpa.show-sql=true", "spring.jpa.hibernate.ddl-auto:create-drop",
"spring.datasource.data:classpath:/city.sql",
"spring.datasource.initialization-order=after-jpa")
"spring.datasource.data:classpath:/city.sql", "spring.jpa.defer-datasource-initialization=true")
.run((context) -> {
// See CityListener
assertThat(context).hasSingleBean(City.class);
@ -418,6 +417,7 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
@Configuration(proxyBeanMethods = false)
@TestAutoConfigurationPackage(City.class)
@DependsOnDataSourceInitialization
static class TestInitializedJpaConfiguration {
private boolean called;

@ -2030,11 +2030,6 @@ In addition, Spring Boot processes the `schema-$\{platform}.sql` and `data-$\{pl
This allows you to switch to database-specific scripts if necessary.
For example, you might choose to set it to the vendor name of the database (`hsqldb`, `h2`, `oracle`, `mysql`, `postgresql`, and so on).
By default, script-based `DataSource` initialization is performed before any JPA `EntityManagerFactory` beans are created.
`schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it.
If you want script-based `DataSource` initialization to be performed after the `EntityManagerFactory`, set `spring.datasource.initialization-order` to `after-jpa`.
`schema.sql` can then be used to make additions to any schema creation performed by Hibernate and `data.sql` can be used to populate it.
[NOTE]
====
When only basic SQL scripts are used, Spring Boot automatically creates the schema of an embedded `DataSource`.
@ -2046,13 +2041,12 @@ For instance, if you want to always initialize the `DataSource` regardless of it
spring.datasource.initialization-mode=always
----
In a JPA-based app, you can choose to let Hibernate create the schema or use `schema.sql`, but you cannot do both.
Make sure to disable `spring.jpa.hibernate.ddl-auto` if you use `schema.sql`.
[indent=0,subs="verbatim,quotes,attributes"]
----
spring.jpa.hibernate.ddl-auto=none
----
By default, script-based `DataSource` initialization is performed before any JPA `EntityManagerFactory` beans are created.
`schema.sql` can be used to create the schema for JPA-managed entities and `data.sql` can be used to populate it.
We do not recommend using multiple data source initialization technologies.
However, if you want script-based `DataSource` initialization to be able to build upon the schema creation performed by Hibernate, set configprop:spring.jpa.defer-datasource-initialization[] to `true`.
This will defer data source initialization until after any `EntityManagerFactory` beans have been created and initialized.
`schema.sql` can then be used to make additions to any schema creation performed by Hibernate and `data.sql` can be used to populate it.
If you are using a <<spring-boot-features.adoc#howto-use-a-higher-level-database-migration-tool,Higher-level Database Migration Tool>>, like Flyway or Liquibase, you should use them alone to create and initialize the schema.
Using the basic `schema.sql` and `data.sql` scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release.
@ -2200,6 +2194,47 @@ See {spring-boot-autoconfigure-module-code}/liquibase/LiquibaseProperties.java[`
[[howto-initialize-a-database-configuring-dependencies]]
=== Depend Upon an Initialized DataSource
DataSource initialization is performed while the application is starting up as part of application context refresh.
To allow an initialized database to be accessed during startup, beans that act as DataSource initializers and beans that
require that DataSource to have been initialized are detected automatically.
Beans whose initialization depends upon the DataSource having been initialized are configured to depend upon those that initialize it.
If, during startup, your application tries to access the database and it has not been initialized, you can configure additional detection of beans that initialize the DataSource and require the DataSource to have been initialized.
[[howto-initialize-a-database-configuring-dependencies-initializer-detection]]
==== Detect a DataSource Initializer
Spring Boot will automatically detect beans of the following types that initialize a `DataSource`:
- `DataSourceInitialization`
- `EntityManagerFactory`
- `Flyway`
- `FlywayMigrationInitializer`
- `SpringLiquibase`
If you are using a third-party starter for a DataSource initialization library, it may provide a detector such that beans of other types are also detected automatically.
To have other beans be detected, register an implementation of `DataSourceInitializerDetector` in `META-INF/spring-factories`.
[[howto-initialize-a-database-configuring-dependencies-depends-on-initialization-detection]]
==== Detect a Bean That Depends On DataSource Initialization
Spring Boot will automatically detect beans of the following types that depends upon `DataSource` initialization:
- `AbstractEntityManagerFactoryBean` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`)
- `DSLContext` (jOOQ)
- `EntityManagerFactory` (unless configprop:spring.jpa.defer-datasource-initialization[] is set to `true`)
- `JdbcOperations`
- `NamedParameterJdbcOperations`
If you are using a third-party starter data access library, it may provide a detector such that beans of other types are also detected automatically.
To have other beans be detected, register an implementation of `DependsOnDataSourceInitializationDetector` in `META-INF/spring-factories`.
Alternatively, annotate the bean's class or its `@Bean` method with `@DependsOnDataSourceInitializationDetector`.
[[howto-messaging]]
== Messaging
Spring Boot offers a number of starters that include messaging.

@ -51,10 +51,12 @@ dependencies {
optional("org.eclipse.jetty:jetty-webapp")
optional("org.eclipse.jetty:jetty-alpn-conscrypt-server")
optional("org.eclipse.jetty.http2:http2-server")
optional("org.flywaydb:flyway-core")
optional("org.hamcrest:hamcrest-library")
optional("org.hibernate:hibernate-core")
optional("org.hibernate.validator:hibernate-validator")
optional("org.jboss:jboss-transaction-spi")
optional("org.jooq:jooq")
optional("org.liquibase:liquibase-core")
optional("org.slf4j:jul-to-slf4j")
optional("org.slf4j:slf4j-api")

@ -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.flyway;
import java.util.Collections;
import java.util.Set;
import org.flywaydb.core.Flyway;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector;
import org.springframework.boot.jdbc.init.DataSourceInitializerDetector;
/**
* A {@link DataSourceInitializerDetector} for {@link Flyway}.
*
* @author Andy Wilkinson
*/
class FlywayDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
@Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
return Collections.singleton(Flyway.class);
}
}

@ -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.
*/
/**
* Custom support for Flyway database migration.
*/
package org.springframework.boot.flyway;

@ -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.jdbc;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
/**
* {@link DependsOnDataSourceInitializationDetector} for Spring Framework's JDBC support.
*
* @author Andy Wilkinson
*/
class SpringJdbcDependsOnDataSourceInitializationDetector
extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDataSourceInitializationBeanTypes() {
return new HashSet<>(Arrays.asList(JdbcOperations.class, NamedParameterJdbcOperations.class));
}
}

@ -0,0 +1,52 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Collections;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Base class for {@link DataSourceInitializerDetector DataSourceInitializerDetectors}
* that detect {@link DataSource} beans by type.
*
* @author Andy Wilkinson
* @since 2.5.0
*/
public abstract class AbstractBeansOfTypeDataSourceInitializerDetector implements DataSourceInitializerDetector {
/**
* Returns the bean types that should be detected as being data source initializers.
* @return the data source initializer bean types
*/
protected abstract Set<Class<?>> getDataSourceInitializerBeanTypes();
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
try {
Set<Class<?>> types = getDataSourceInitializerBeanTypes();
return new BeansOfTypeDetector(types).detect(beanFactory);
}
catch (Throwable ex) {
return Collections.emptySet();
}
}
}

@ -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.jdbc.init;
import java.util.Collections;
import java.util.Set;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Base class for {@link DependsOnDataSourceInitializationDetector
* InitializedDataSourceDependentDetector} that detect by type beans that depend upon data
* source initialization.
*
* @author Andy Wilkinson
* @since 2.5.0
*/
public abstract class AbstractBeansOfTypeDependsOnDataSourceInitializationDetector
implements DependsOnDataSourceInitializationDetector {
/**
* Returns the bean types that should be detected as depending on data source
* initialization.
* @return the data source initialization dependent bean types
*/
protected abstract Set<Class<?>> getDependsOnDataSourceInitializationBeanTypes();
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
try {
Set<Class<?>> types = getDependsOnDataSourceInitializationBeanTypes();
return new BeansOfTypeDetector(types).detect(beanFactory);
}
catch (Throwable ex) {
return Collections.emptySet();
}
}
}

@ -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.jdbc.init;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* {@link DependsOnDataSourceInitializationDetector} that detects beans annotated with
* {@link DependsOnDataSourceInitialization}.
*
* @author Andy Wilkinson
*/
class AnnotationDependsOnDataSourceInitializationDetector implements DependsOnDataSourceInitializationDetector {
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
Set<String> dependentBeans = new HashSet<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
if (beanFactory.findAnnotationOnBean(beanName, DependsOnDataSourceInitialization.class) != 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.jdbc.init;
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,157 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
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.boot.util.Instantiator;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;
/**
* Configures beans that depend upon DataSource initialization with
* {@link BeanDefinition#getDependsOn()} dependencies upon beans that perform
* {@link DataSource} initialization. Intended for {@link Import import} in configuration
* classes that define {@code DataSource} initialization beans or that define beans that
* require DataSource initialization to have completed before they are initialized.
* <p>
* Beans that initialize a {@link DataSource} are identified by
* {@link DataSourceInitializerDetector DataSourceInitializerDetectors}. Beans that depend
* upon DataSource initialization are identified by
* {@link DependsOnDataSourceInitializationDetector
* DependsOnDataSourceInitializationDetectors}.
*
* @author Andy Wilkinson
* @since 2.5.0
* @see DataSourceInitializerDetector
* @see DependsOnDataSourceInitializationDetector
* @see DependsOnDataSourceInitialization
*/
public class DataSourceInitializationDependencyConfigurer implements ImportBeanDefinitionRegistrar {
private final Environment environment;
DataSourceInitializationDependencyConfigurer(Environment environment) {
this.environment = environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class.getName())) {
return;
}
registry.registerBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class.getName(),
BeanDefinitionBuilder
.genericBeanDefinition(DependsOnDataSourceInitializationPostProcessor.class,
() -> new DependsOnDataSourceInitializationPostProcessor(this.environment))
.getBeanDefinition());
}
static class DependsOnDataSourceInitializationPostProcessor implements BeanFactoryPostProcessor {
private final Environment environment;
DependsOnDataSourceInitializationPostProcessor(Environment environment) {
this.environment = environment;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
Set<String> detectedDataSourceInitializers = detectDataSourceInitializers(beanFactory);
for (String dependentDefinitionName : detectDependsOnDataSourceInitialization(beanFactory,
this.environment)) {
BeanDefinition definition = getBeanDefinition(dependentDefinitionName, beanFactory);
String[] dependencies = definition.getDependsOn();
for (String dependencyName : detectedDataSourceInitializers) {
dependencies = StringUtils.addStringToArray(dependencies, dependencyName);
}
definition.setDependsOn(dependencies);
}
}
private Set<String> detectDataSourceInitializers(ConfigurableListableBeanFactory beanFactory) {
List<DataSourceInitializerDetector> detectors = instantiateDetectors(beanFactory, this.environment,
DataSourceInitializerDetector.class);
Set<String> detected = new HashSet<>();
for (DataSourceInitializerDetector detector : detectors) {
for (String initializerName : detector.detect(beanFactory)) {
detected.add(initializerName);
beanFactory.getBeanDefinition(initializerName)
.setAttribute(DataSourceInitializerDetector.class.getName(), detector.getClass().getName());
}
}
detected = Collections.unmodifiableSet(detected);
for (DataSourceInitializerDetector detector : detectors) {
detector.detectionComplete(beanFactory, detected);
}
return detected;
}
private Collection<String> detectDependsOnDataSourceInitialization(ConfigurableListableBeanFactory beanFactory,
Environment environment) {
List<DependsOnDataSourceInitializationDetector> detectors = instantiateDetectors(beanFactory, environment,
DependsOnDataSourceInitializationDetector.class);
Set<String> dependentUponDataSourceInitialization = new HashSet<>();
for (DependsOnDataSourceInitializationDetector detector : detectors) {
dependentUponDataSourceInitialization.addAll(detector.detect(beanFactory));
}
return dependentUponDataSourceInitialization;
}
private <T> List<T> instantiateDetectors(ConfigurableListableBeanFactory beanFactory, Environment environment,
Class<T> detectorType) {
List<String> detectorNames = SpringFactoriesLoader.loadFactoryNames(detectorType,
beanFactory.getBeanClassLoader());
Instantiator<T> instantiator = new Instantiator<>(detectorType,
(availableParameters) -> availableParameters.add(Environment.class, environment));
List<T> detectors = instantiator.instantiate(detectorNames);
return detectors;
}
private static BeanDefinition getBeanDefinition(String beanName, ConfigurableListableBeanFactory beanFactory) {
try {
return beanFactory.getBeanDefinition(beanName);
}
catch (NoSuchBeanDefinitionException ex) {
BeanFactory parentBeanFactory = beanFactory.getParentBeanFactory();
if (parentBeanFactory instanceof ConfigurableListableBeanFactory) {
return getBeanDefinition(beanName, (ConfigurableListableBeanFactory) parentBeanFactory);
}
throw ex;
}
}
}
}

@ -0,0 +1,55 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Detects beans that initialize a {@link DataSource}. Implementations should be
* registered in {@code META-INF/spring.factories} under the key
* {@code org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector}.
*
* @author Andy Wilkinson
* @since 2.5.0
*/
public interface DataSourceInitializerDetector {
/**
* 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) {
}
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.jdbc;
package org.springframework.boot.jdbc.init;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
@ -22,14 +22,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.sql.DataSource;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Bean;
/**
* Annotation used to indicate that a bean depends upon the auto-configured DataSource
* initialization having completed before the {@link DataSource} is injected. If the
* DataSource is not used during startup, no such dependency is required.
* Indicate that a bean's creation and initialization depends upon data source
* initialization having completed. May be used on a bean's class or its
* {@link Bean @Bean} definition.
*
* @author Andy Wilkinson
* @since 2.5.0
@ -37,7 +35,6 @@ import org.springframework.context.annotation.DependsOn;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@DependsOn("dataSourceInitialization")
public @interface DependsOnDataSourceInitialization {
}

@ -0,0 +1,44 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Detects beans that depend on {@link DataSource} initialization. Implementations should
* be registered in {@code META-INF/spring.factories} under the key
* {@code org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector}.
*
* @author Andy Wilkinson
* @since 2.5.0
*/
public interface DependsOnDataSourceInitializationDetector {
/**
* Detect beans defined in the given {@code beanFactory} that depend on
* {@link DataSource} 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 {@code DataSource} 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.
*/
/**
* General infrastructure for {@code DataSource} initialization.
*/
package org.springframework.boot.jdbc.init;

@ -0,0 +1,40 @@
/*
* 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.jooq;
import java.util.Collections;
import java.util.Set;
import org.jooq.DSLContext;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
/**
* {@link DependsOnDataSourceInitializationDetector} for jOOQ.
*
* @author Andy Wilkinson
*/
class JooqDependsOnDataSourceInitializationDetector
extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDataSourceInitializationBeanTypes() {
return Collections.singleton(DSLContext.class);
}
}

@ -0,0 +1,22 @@
/*
* 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 jOOQ.
*
* @see org.springframework.boot.json.JsonParser
*/
package org.springframework.boot.jooq;

@ -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.liquibase;
import java.util.Collections;
import java.util.Set;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector;
import org.springframework.boot.jdbc.init.DataSourceInitializerDetector;
/**
* A {@link DataSourceInitializerDetector} for Liquibase.
*
* @author Andy Wilkinson
*/
class LiquibaseDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
@Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
return Collections.singleton(SpringLiquibase.class);
}
}

@ -0,0 +1,82 @@
/*
* 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.orm.jpa;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.persistence.EntityManagerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDataSourceInitializerDetector;
import org.springframework.boot.jdbc.init.DataSourceInitializerDetector;
import org.springframework.core.env.Environment;
import org.springframework.util.StringUtils;
/**
* A {@link DataSourceInitializerDetector} for JPA.
*
* @author Andy Wilkinson
*/
class JpaDataSourceInitializerDetector extends AbstractBeansOfTypeDataSourceInitializerDetector {
private final Environment environment;
JpaDataSourceInitializerDetector(Environment environment) {
this.environment = environment;
}
@Override
protected Set<Class<?>> getDataSourceInitializerBeanTypes() {
boolean deferred = this.environment.getProperty("spring.jpa.defer-datasource-initialization", boolean.class,
false);
return deferred ? Collections.singleton(EntityManagerFactory.class) : Collections.emptySet();
}
@Override
public void detectionComplete(ConfigurableListableBeanFactory beanFactory, Set<String> dataSourceInitializerNames) {
configureOtherInitializersToDependOnJpaInitializers(beanFactory, dataSourceInitializerNames);
}
private void configureOtherInitializersToDependOnJpaInitializers(ConfigurableListableBeanFactory beanFactory,
Set<String> dataSourceInitializerNames) {
Set<String> jpaInitializers = new HashSet<>();
Set<String> otherInitializers = new HashSet<>(dataSourceInitializerNames);
Iterator<String> iterator = otherInitializers.iterator();
while (iterator.hasNext()) {
String initializerName = iterator.next();
BeanDefinition initializerDefinition = beanFactory.getBeanDefinition(initializerName);
if (JpaDataSourceInitializerDetector.class.getName()
.equals(initializerDefinition.getAttribute(DataSourceInitializerDetector.class.getName()))) {
iterator.remove();
jpaInitializers.add(initializerName);
}
}
for (String otherInitializerName : otherInitializers) {
BeanDefinition definition = beanFactory.getBeanDefinition(otherInitializerName);
String[] dependencies = definition.getDependsOn();
for (String dependencyName : jpaInitializers) {
dependencies = StringUtils.addStringToArray(dependencies, dependencyName);
}
definition.setDependsOn(dependencies);
}
}
}

@ -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.orm.jpa;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.EntityManagerFactory;
import org.springframework.boot.jdbc.init.AbstractBeansOfTypeDependsOnDataSourceInitializationDetector;
import org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
/**
* {@link DependsOnDataSourceInitializationDetector} for JPA.
*
* @author Andy Wilkinson
*/
class JpaDependsOnDataSourceInitializationDetector
extends AbstractBeansOfTypeDependsOnDataSourceInitializationDetector {
private final Environment environment;
JpaDependsOnDataSourceInitializationDetector(Environment environment) {
this.environment = environment;
}
@Override
protected Set<Class<?>> getDependsOnDataSourceInitializationBeanTypes() {
boolean postpone = this.environment.getProperty("spring.jpa.defer-datasource-initialization", boolean.class,
false);
return postpone ? Collections.emptySet()
: new HashSet<>(Arrays.asList(EntityManagerFactory.class, AbstractEntityManagerFactoryBean.class));
}
}

@ -316,6 +316,11 @@
"description": "Whether to enable configuration data processing legacy mode.",
"defaultValue": false
},
{
"name": "spring.jpa.defer-datasource-initialization",
"type": "java.lang.Boolean",
"defaultValue": false
},
{
"name": "spring.jta.atomikos.connectionfactory.borrow-connection-timeout",
"type": "java.lang.Integer",

@ -78,3 +78,16 @@ org.springframework.boot.liquibase.LiquibaseChangelogMissingFailureAnalyzer
# Failure Analysis Reporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
# DataSource Initializer Detectors
org.springframework.boot.jdbc.init.DataSourceInitializerDetector=\
org.springframework.boot.flyway.FlywayDataSourceInitializerDetector,\
org.springframework.boot.liquibase.LiquibaseDataSourceInitializerDetector,\
org.springframework.boot.orm.jpa.JpaDataSourceInitializerDetector
# Depends On DataSource Initialization Detectors
org.springframework.boot.jdbc.init.DependsOnDataSourceInitializationDetector=\
org.springframework.boot.jdbc.init.AnnotationDependsOnDataSourceInitializationDetector,\
org.springframework.boot.jdbc.SpringJdbcDependsOnDataSourceInitializationDetector,\
org.springframework.boot.jooq.JooqDependsOnDataSourceInitializationDetector,\
org.springframework.boot.orm.jpa.JpaDependsOnDataSourceInitializationDetector

@ -0,0 +1,232 @@
/*
* Copyright 2012-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.jdbc.init;
import java.io.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.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import org.springframework.beans.factory.config.BeanDefinition;
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.Configuration;
import org.springframework.context.annotation.Import;
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.Mockito.reset;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link DataSourceInitializationDependencyConfigurer}.
*
* @author Andy Wilkinson
*/
class DataSourceInitializationDependencyConfigurerTests {
private final ConfigurableEnvironment environment = new MockEnvironment();
DataSourceInitializerDetector dataSourceInitializerDetector = MockedDataSourceInitializerDetector.mock;
DependsOnDataSourceInitializationDetector dependsOnDataSourceInitializationDetector = MockedDependsOnDataSourceInitializationDetector.mock;
@TempDir
File temp;
@BeforeEach
void resetMocks() {
reset(MockedDataSourceInitializerDetector.mock, MockedDependsOnDataSourceInitializationDetector.mock);
}
@Test
void whenDetectorsAreCreatedThenTheEnvironmentCanBeInjected() {
performDetection(Arrays.asList(ConstructorInjectionDataSourceInitializerDetector.class,
ConstructorInjectionDependsOnDataSourceInitializationDetector.class), (context) -> {
context.refresh();
assertThat(ConstructorInjectionDataSourceInitializerDetector.environment)
.isEqualTo(this.environment);
assertThat(ConstructorInjectionDependsOnDataSourceInitializationDetector.environment)
.isEqualTo(this.environment);
});
}
@Test
void whenDependenciesAreConfiguredThenBeansThatDependUponDataSourceInitializationDependUponDetectedDataSourceInitializers() {
BeanDefinition alpha = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
BeanDefinition bravo = BeanDefinitionBuilder.genericBeanDefinition(String.class).getBeanDefinition();
performDetection(Arrays.asList(MockedDataSourceInitializerDetector.class,
MockedDependsOnDataSourceInitializationDetector.class), (context) -> {
context.registerBeanDefinition("alpha", alpha);
context.registerBeanDefinition("bravo", bravo);
given(this.dataSourceInitializerDetector.detect(context.getBeanFactory()))
.willReturn(Collections.singleton("alpha"));
given(this.dependsOnDataSourceInitializationDetector.detect(context.getBeanFactory()))
.willReturn(Collections.singleton("bravo"));
context.refresh();
assertThat(alpha.getAttribute(DataSourceInitializerDetector.class.getName()))
.isEqualTo(MockedDataSourceInitializerDetector.class.getName());
assertThat(bravo.getAttribute(DataSourceInitializerDetector.class.getName())).isNull();
verify(this.dataSourceInitializerDetector).detectionComplete(context.getBeanFactory(),
Collections.singleton("alpha"));
assertThat(bravo.getDependsOn()).containsExactly("alpha");
});
}
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);
context.register(DependencyConfigurerConfiguration.class);
contextCallback.accept(context);
}
}
@Configuration(proxyBeanMethods = false)
@Import(DataSourceInitializationDependencyConfigurer.class)
static class DependencyConfigurerConfiguration {
}
static class ConstructorInjectionDataSourceInitializerDetector implements DataSourceInitializerDetector {
private static Environment environment;
ConstructorInjectionDataSourceInitializerDetector(Environment environment) {
ConstructorInjectionDataSourceInitializerDetector.environment = environment;
}
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return Collections.emptySet();
}
}
static class ConstructorInjectionDependsOnDataSourceInitializationDetector
implements DependsOnDataSourceInitializationDetector {
private static Environment environment;
ConstructorInjectionDependsOnDataSourceInitializationDetector(Environment environment) {
ConstructorInjectionDependsOnDataSourceInitializationDetector.environment = environment;
}
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return Collections.emptySet();
}
}
static class MockedDataSourceInitializerDetector implements DataSourceInitializerDetector {
private static DataSourceInitializerDetector mock = Mockito.mock(DataSourceInitializerDetector.class);
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return MockedDataSourceInitializerDetector.mock.detect(beanFactory);
}
@Override
public void detectionComplete(ConfigurableListableBeanFactory beanFactory,
Set<String> dataSourceInitializerNames) {
mock.detectionComplete(beanFactory, dataSourceInitializerNames);
}
}
static class MockedDependsOnDataSourceInitializationDetector implements DependsOnDataSourceInitializationDetector {
private static DependsOnDataSourceInitializationDetector mock = Mockito
.mock(DependsOnDataSourceInitializationDetector.class);
@Override
public Set<String> detect(ConfigurableListableBeanFactory beanFactory) {
return MockedDependsOnDataSourceInitializationDetector.mock.detect(beanFactory);
}
}
static class DetectorSpringFactoriesClassLoader extends ClassLoader {
private final Set<Class<DataSourceInitializerDetector>> dataSourceInitializerDetectors = new HashSet<>();
private final Set<Class<DependsOnDataSourceInitializationDetector>> dependsOnDataSourceInitializationDetectors = new HashSet<>();
private final File temp;
DetectorSpringFactoriesClassLoader(File temp) {
this.temp = temp;
}
@SuppressWarnings("unchecked")
void register(Class<?> detector) {
if (DataSourceInitializerDetector.class.isAssignableFrom(detector)) {
this.dataSourceInitializerDetectors.add((Class<DataSourceInitializerDetector>) detector);
}
else if (DependsOnDataSourceInitializationDetector.class.isAssignableFrom(detector)) {
this.dependsOnDataSourceInitializationDetectors
.add((Class<DependsOnDataSourceInitializationDetector>) 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(DataSourceInitializerDetector.class.getName(), String.join(",",
this.dataSourceInitializerDetectors.stream().map(Class::getName).collect(Collectors.toList())));
properties.put(DependsOnDataSourceInitializationDetector.class.getName(),
String.join(",", this.dependsOnDataSourceInitializationDetectors.stream().map(Class::getName)
.collect(Collectors.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()));
}
}
}

@ -1,2 +1,2 @@
spring.test.mockmvc.print=none
spring.datasource.initialization-order=after-jpa
spring.jpa.defer-datasource-initialization=true

Loading…
Cancel
Save