Rework DataSource initialization
Previously, DataSource initialization was triggered via a BeanPostProcessor or a schema created event from JPA. This caused numerous problems with circular dependencies, bean lifecycle, etc and added significant complexity. This commit reworks DataSource initialization to remove the use of a BeanPostProcessor entirely. In its place, DataSource initialization is now driven by an InitializingBean with dependency relationships between beans ensuring that initialization has been performed before the DataSource is used. This aligns with the approach that's worked well with Flyway and Liquibase. More changes are planned to further simplify DataSource initialization. The changes in this commit are a foundation for those changes. Any new public API in this commit is highly likely to change before the next GA. Fixes gh-13042 Fixes gh-23736
This commit is contained in:
parent
b3d73a1d06
commit
2f83a6714b
|
@ -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.autoconfigure.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ResourceLoaderAware;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
||||
/**
|
||||
* {@link InitializingBean} that performs {@link DataSource} initialization using DDL and
|
||||
* DML scripts.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
public class DataSourceInitialization implements InitializingBean, ResourceLoaderAware {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
||||
private final DataSourceProperties properies;
|
||||
|
||||
private volatile ResourceLoader resourceLoader;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DataSourceInitialization} that will initialize the given
|
||||
* {@code DataSource} using the settings from the given {@code properties}.
|
||||
* @param dataSource the DataSource to initialize
|
||||
* @param properies the properties containing the initialization settings
|
||||
*/
|
||||
public DataSourceInitialization(DataSource dataSource, DataSourceProperties properies) {
|
||||
this.dataSource = dataSource;
|
||||
this.properies = properies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
new DataSourceInitializer(this.dataSource, this.properies, this.resourceLoader).initializeDataSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -16,47 +16,82 @@
|
|||
|
||||
package org.springframework.boot.autoconfigure.jdbc;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
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.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
/**
|
||||
* Configures DataSource initialization.
|
||||
* Configuration for {@link DataSource} initialization using DDL and DML scripts.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
|
||||
@ConditionalOnSingleCandidate(DataSource.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to register the
|
||||
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
|
||||
* issues.
|
||||
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
|
||||
* {@link DataSourceInitialization} beans.
|
||||
*/
|
||||
static class Registrar implements ImportBeanDefinitionRegistrar {
|
||||
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
|
||||
static class DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor
|
||||
extends EntityManagerFactoryDependsOnPostProcessor {
|
||||
|
||||
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
|
||||
DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor() {
|
||||
super(DataSourceInitialization.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(BEAN_NAME)) {
|
||||
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DataSourceInitializerPostProcessor.class,
|
||||
DataSourceInitializerPostProcessor::new)
|
||||
.getBeanDefinition();
|
||||
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
// We don't need this one to be post processed otherwise it can cause a
|
||||
// cascade of bean instantiation that we would rather avoid.
|
||||
beanDefinition.setSynthetic(true);
|
||||
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -46,8 +46,9 @@ import org.springframework.util.StringUtils;
|
|||
* @author Eddú Meléndez
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
* @since 2.5.0
|
||||
*/
|
||||
class DataSourceInitializer {
|
||||
public class DataSourceInitializer {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
|
||||
|
||||
|
@ -64,32 +65,25 @@ class DataSourceInitializer {
|
|||
* @param properties the matching configuration
|
||||
* @param resourceLoader the resource loader to use (can be null)
|
||||
*/
|
||||
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties, ResourceLoader resourceLoader) {
|
||||
public DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
|
||||
ResourceLoader resourceLoader) {
|
||||
this.dataSource = dataSource;
|
||||
this.properties = properties;
|
||||
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance with the {@link DataSource} to initialize and its matching
|
||||
* {@link DataSourceProperties configuration}.
|
||||
* @param dataSource the datasource to initialize
|
||||
* @param properties the matching configuration
|
||||
* Initializes the {@link DataSource} by running DDL and DML scripts.
|
||||
* @return {@code true} if one or more scripts were applied to the database, otherwise
|
||||
* {@code false}
|
||||
*/
|
||||
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
|
||||
this(dataSource, properties, null);
|
||||
public boolean initializeDataSource() {
|
||||
boolean initialized = createSchema();
|
||||
initialized = initSchema() && initialized;
|
||||
return initialized;
|
||||
}
|
||||
|
||||
DataSource getDataSource() {
|
||||
return this.dataSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the schema if necessary.
|
||||
* @return {@code true} if the schema was created
|
||||
* @see DataSourceProperties#getSchema()
|
||||
*/
|
||||
boolean createSchema() {
|
||||
private boolean createSchema() {
|
||||
List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
|
||||
if (!scripts.isEmpty()) {
|
||||
if (!isEnabled()) {
|
||||
|
@ -103,21 +97,18 @@ class DataSourceInitializer {
|
|||
return !scripts.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the schema if necessary.
|
||||
* @see DataSourceProperties#getData()
|
||||
*/
|
||||
void initSchema() {
|
||||
private boolean initSchema() {
|
||||
List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data");
|
||||
if (!scripts.isEmpty()) {
|
||||
if (!isEnabled()) {
|
||||
logger.debug("Initialization disabled (not running data scripts)");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
String username = this.properties.getDataUsername();
|
||||
String password = this.properties.getDataPassword();
|
||||
runScripts(scripts, username, password);
|
||||
}
|
||||
return !scripts.isEmpty();
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
|
||||
/**
|
||||
* Bean to handle {@link DataSource} initialization by running {@literal schema-*.sql} on
|
||||
* {@link InitializingBean#afterPropertiesSet()} and {@literal data-*.sql} SQL scripts on
|
||||
* a {@link DataSourceSchemaCreatedEvent}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @see DataSourceAutoConfiguration
|
||||
*/
|
||||
class DataSourceInitializerInvoker implements ApplicationListener<DataSourceSchemaCreatedEvent>, InitializingBean {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(DataSourceInitializerInvoker.class);
|
||||
|
||||
private final ObjectProvider<DataSource> dataSource;
|
||||
|
||||
private final DataSourceProperties properties;
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private DataSourceInitializer dataSourceInitializer;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
DataSourceInitializerInvoker(ObjectProvider<DataSource> dataSource, DataSourceProperties properties,
|
||||
ApplicationContext applicationContext) {
|
||||
this.dataSource = dataSource;
|
||||
this.properties = properties;
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
DataSourceInitializer initializer = getDataSourceInitializer();
|
||||
if (initializer != null) {
|
||||
boolean schemaCreated = this.dataSourceInitializer.createSchema();
|
||||
if (schemaCreated) {
|
||||
initialize(initializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initialize(DataSourceInitializer initializer) {
|
||||
try {
|
||||
this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(initializer.getDataSource()));
|
||||
// The listener might not be registered yet, so don't rely on it.
|
||||
if (!this.initialized) {
|
||||
this.dataSourceInitializer.initSchema();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
logger.warn(LogMessage.format("Could not send event to complete DataSource initialization (%s)",
|
||||
ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(DataSourceSchemaCreatedEvent event) {
|
||||
// NOTE the event can happen more than once and
|
||||
// the event datasource is not used here
|
||||
DataSourceInitializer initializer = getDataSourceInitializer();
|
||||
if (!this.initialized && initializer != null) {
|
||||
initializer.initSchema();
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private DataSourceInitializer getDataSourceInitializer() {
|
||||
if (this.dataSourceInitializer == null) {
|
||||
DataSource ds = this.dataSource.getIfUnique();
|
||||
if (ds != null) {
|
||||
this.dataSourceInitializer = new DataSourceInitializer(ds, this.properties, this.applicationContext);
|
||||
}
|
||||
}
|
||||
return this.dataSourceInitializer;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.jdbc;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} used to ensure that {@link DataSourceInitializer} is
|
||||
* initialized as soon as a {@link DataSource} is.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class DataSourceInitializerPostProcessor implements BeanPostProcessor, Ordered, BeanFactoryAware {
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE + 1;
|
||||
}
|
||||
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
// force initialization of this bean as soon as we see a DataSource
|
||||
this.beanFactory.getBean(DataSourceInitializerInvoker.class);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -28,8 +28,10 @@ import org.springframework.context.ApplicationEvent;
|
|||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0.0
|
||||
* @deprecated since 2.5.0 with no replacement as the event is no longer published
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
@Deprecated
|
||||
public class DataSourceSchemaCreatedEvent extends ApplicationEvent {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.autoconfigure.jdbc;
|
||||
|
||||
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 javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 2.5.0
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@DependsOn("dataSourceInitialization")
|
||||
public @interface DependsOnDataSourceInitialization {
|
||||
|
||||
}
|
|
@ -1,271 +0,0 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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 java.util.concurrent.Future;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.spi.PersistenceProvider;
|
||||
import javax.persistence.spi.PersistenceUnitInfo;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceSchemaCreatedEvent;
|
||||
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
||||
/**
|
||||
* {@link BeanPostProcessor} used to fire {@link DataSourceSchemaCreatedEvent}s. Should
|
||||
* only be registered via the inner {@link Registrar} class.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class DataSourceInitializedPublisher implements BeanPostProcessor {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private JpaProperties jpaProperties;
|
||||
|
||||
private HibernateProperties hibernateProperties;
|
||||
|
||||
private DataSourceSchemaCreatedPublisher schemaCreatedPublisher;
|
||||
|
||||
private DataSourceInitializationCompletionListener initializationCompletionListener;
|
||||
|
||||
DataSourceInitializedPublisher(DataSourceInitializationCompletionListener completionListener) {
|
||||
this.initializationCompletionListener = completionListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof LocalContainerEntityManagerFactoryBean) {
|
||||
LocalContainerEntityManagerFactoryBean factory = (LocalContainerEntityManagerFactoryBean) bean;
|
||||
if (factory.getBootstrapExecutor() != null && factory.getJpaVendorAdapter() != null) {
|
||||
this.schemaCreatedPublisher = new DataSourceSchemaCreatedPublisher(factory);
|
||||
factory.setJpaVendorAdapter(this.schemaCreatedPublisher);
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
// Normally this will be the right DataSource
|
||||
this.dataSource = (DataSource) bean;
|
||||
}
|
||||
if (bean instanceof JpaProperties) {
|
||||
this.jpaProperties = (JpaProperties) bean;
|
||||
}
|
||||
if (bean instanceof HibernateProperties) {
|
||||
this.hibernateProperties = (HibernateProperties) bean;
|
||||
}
|
||||
if (bean instanceof LocalContainerEntityManagerFactoryBean && this.schemaCreatedPublisher == null) {
|
||||
LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean;
|
||||
EntityManagerFactory entityManagerFactory = factoryBean.getNativeEntityManagerFactory();
|
||||
publishEventIfRequired(factoryBean, entityManagerFactory);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private void publishEventIfRequired(LocalContainerEntityManagerFactoryBean factoryBean,
|
||||
EntityManagerFactory entityManagerFactory) {
|
||||
DataSource dataSource = findDataSource(factoryBean, entityManagerFactory);
|
||||
if (dataSource != null && isInitializingDatabase(dataSource)) {
|
||||
this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(dataSource));
|
||||
}
|
||||
}
|
||||
|
||||
private DataSource findDataSource(LocalContainerEntityManagerFactoryBean factoryBean,
|
||||
EntityManagerFactory entityManagerFactory) {
|
||||
Object dataSource = entityManagerFactory.getProperties().get("javax.persistence.nonJtaDataSource");
|
||||
if (dataSource == null) {
|
||||
dataSource = factoryBean.getPersistenceUnitInfo().getNonJtaDataSource();
|
||||
}
|
||||
return (dataSource instanceof DataSource) ? (DataSource) dataSource : this.dataSource;
|
||||
}
|
||||
|
||||
private boolean isInitializingDatabase(DataSource dataSource) {
|
||||
if (this.jpaProperties == null || this.hibernateProperties == null) {
|
||||
return true; // better safe than sorry
|
||||
}
|
||||
Supplier<String> defaultDdlAuto = () -> (EmbeddedDatabaseConnection.isEmbedded(dataSource) ? "create-drop"
|
||||
: "none");
|
||||
Map<String, Object> hibernate = this.hibernateProperties.determineHibernateProperties(
|
||||
this.jpaProperties.getProperties(), new HibernateSettings().ddlAuto(defaultDdlAuto));
|
||||
return hibernate.containsKey("hibernate.hbm2ddl.auto");
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ApplicationListener} that, upon receiving {@link ContextRefreshedEvent},
|
||||
* blocks until any asynchronous DataSource initialization has completed.
|
||||
*/
|
||||
static class DataSourceInitializationCompletionListener
|
||||
implements ApplicationListener<ContextRefreshedEvent>, Ordered, ApplicationContextAware {
|
||||
|
||||
private volatile ApplicationContext applicationContext;
|
||||
|
||||
private volatile Future<?> dataSourceInitialization;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (!event.getApplicationContext().equals(this.applicationContext)) {
|
||||
return;
|
||||
}
|
||||
Future<?> dataSourceInitialization = this.dataSourceInitialization;
|
||||
if (dataSourceInitialization != null) {
|
||||
try {
|
||||
dataSourceInitialization.get();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to register the
|
||||
* {@link DataSourceInitializedPublisher} without causing early bean instantiation
|
||||
* issues.
|
||||
*/
|
||||
static class Registrar implements ImportBeanDefinitionRegistrar {
|
||||
|
||||
private static final String PUBLISHER_BEAN_NAME = "dataSourceInitializedPublisher";
|
||||
|
||||
private static final String COMPLETION_LISTENER_BEAN_BEAN = DataSourceInitializationCompletionListener.class
|
||||
.getName();
|
||||
|
||||
@Override
|
||||
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
|
||||
BeanDefinitionRegistry registry) {
|
||||
if (!registry.containsBeanDefinition(PUBLISHER_BEAN_NAME)) {
|
||||
DataSourceInitializationCompletionListener completionListener = new DataSourceInitializationCompletionListener();
|
||||
DataSourceInitializedPublisher publisher = new DataSourceInitializedPublisher(completionListener);
|
||||
AbstractBeanDefinition publisherDefinition = BeanDefinitionBuilder
|
||||
.genericBeanDefinition(DataSourceInitializedPublisher.class, () -> publisher)
|
||||
.getBeanDefinition();
|
||||
publisherDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
// We don't need this one to be post processed otherwise it can cause a
|
||||
// cascade of bean instantiation that we would rather avoid.
|
||||
publisherDefinition.setSynthetic(true);
|
||||
registry.registerBeanDefinition(PUBLISHER_BEAN_NAME, publisherDefinition);
|
||||
AbstractBeanDefinition listenerDefinition = BeanDefinitionBuilder.genericBeanDefinition(
|
||||
DataSourceInitializationCompletionListener.class, () -> completionListener).getBeanDefinition();
|
||||
listenerDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
// We don't need this one to be post processed otherwise it can cause a
|
||||
// cascade of bean instantiation that we would rather avoid.
|
||||
listenerDefinition.setSynthetic(true);
|
||||
registry.registerBeanDefinition(COMPLETION_LISTENER_BEAN_BEAN, listenerDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class DataSourceSchemaCreatedPublisher implements JpaVendorAdapter {
|
||||
|
||||
private final LocalContainerEntityManagerFactoryBean factoryBean;
|
||||
|
||||
private final JpaVendorAdapter delegate;
|
||||
|
||||
private DataSourceSchemaCreatedPublisher(LocalContainerEntityManagerFactoryBean factoryBean) {
|
||||
this.factoryBean = factoryBean;
|
||||
this.delegate = factoryBean.getJpaVendorAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceProvider getPersistenceProvider() {
|
||||
return this.delegate.getPersistenceProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPersistenceProviderRootPackage() {
|
||||
return this.delegate.getPersistenceProviderRootPackage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getJpaPropertyMap(PersistenceUnitInfo pui) {
|
||||
return this.delegate.getJpaPropertyMap(pui);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, ?> getJpaPropertyMap() {
|
||||
return this.delegate.getJpaPropertyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JpaDialect getJpaDialect() {
|
||||
return this.delegate.getJpaDialect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends EntityManagerFactory> getEntityManagerFactoryInterface() {
|
||||
return this.delegate.getEntityManagerFactoryInterface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends EntityManager> getEntityManagerInterface() {
|
||||
return this.delegate.getEntityManagerInterface();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
|
||||
this.delegate.postProcessEntityManagerFactory(entityManagerFactory);
|
||||
AsyncTaskExecutor bootstrapExecutor = this.factoryBean.getBootstrapExecutor();
|
||||
if (bootstrapExecutor != null) {
|
||||
DataSourceInitializedPublisher.this.initializationCompletionListener.dataSourceInitialization = bootstrapExecutor
|
||||
.submit(() -> DataSourceInitializedPublisher.this.publishEventIfRequired(this.factoryBean,
|
||||
entityManagerFactory));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -17,16 +17,23 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
|
@ -45,4 +52,18 @@ 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,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -44,7 +44,6 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
|||
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
|
@ -74,7 +73,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(JpaProperties.class)
|
||||
@Import(DataSourceInitializedPublisher.Registrar.class)
|
||||
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
|
||||
|
||||
private final DataSource dataSource;
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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,6 +756,10 @@
|
|||
"name": "spring.datasource.initialization-mode",
|
||||
"defaultValue": "embedded"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.initialization-order",
|
||||
"defaultValue": "before-jpa"
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.jmx-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
@ -1901,6 +1905,17 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.initialization-order",
|
||||
"values": [
|
||||
{
|
||||
"value": "before-jpa"
|
||||
},
|
||||
{
|
||||
"value": "after-jpa"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "spring.datasource.schema",
|
||||
"providers": [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -274,6 +274,7 @@ class DataSourceAutoConfigurationTests {
|
|||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@DependsOnDataSourceInitialization
|
||||
static class TestInitializedDataSourceConfiguration {
|
||||
|
||||
private boolean called;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
|
@ -17,25 +17,36 @@
|
|||
package org.springframework.boot.autoconfigure.jdbc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLFeatureNotSupportedException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.support.SimpleThreadScope;
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
|
@ -50,12 +61,12 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link DataSourceInitializerInvoker}.
|
||||
* Integration tests for DataSource initialization.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class DataSourceInitializerInvokerTests {
|
||||
class DataSourceInitializationIntegrationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
|
||||
|
@ -151,7 +162,6 @@ class DataSourceInitializerInvokerTests {
|
|||
return (context) -> {
|
||||
assertThat(context).hasSingleBean(DataSource.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
context.publishEvent(new DataSourceSchemaCreatedEvent(dataSource));
|
||||
assertDataSourceNotInitialized(dataSource);
|
||||
};
|
||||
}
|
||||
|
@ -238,6 +248,31 @@ class DataSourceInitializerInvokerTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenDataSourceIsProxiedByABeanPostProcessorThenDataSourceInitializationUsesTheProxy() {
|
||||
this.contextRunner.withPropertyValues("spring.datasource.initialization-mode:always")
|
||||
.withUserConfiguration(DataSourceProxyConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(DataSource.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(DataSourceProxy.class);
|
||||
assertThat(((DataSourceProxy) dataSource).connectionsRetrieved).hasPositiveValue();
|
||||
assertDataSourceIsInitialized(dataSource);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
// gh-13042
|
||||
void whenDataSourceIsScopedAndJpaIsInvolvedThenInitializationCompletesSuccessfully() {
|
||||
this.contextRunner.withPropertyValues("spring.datasource.initialization-mode:always")
|
||||
.withConfiguration(AutoConfigurations.of(HibernateJpaAutoConfiguration.class))
|
||||
.withUserConfiguration(ScopedDataSourceConfiguration.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(DataSource.class);
|
||||
DataSource dataSource = context.getBean(DataSource.class);
|
||||
assertThat(dataSource).isInstanceOf(HikariDataSource.class);
|
||||
assertDataSourceIsInitialized(dataSource);
|
||||
});
|
||||
}
|
||||
|
||||
private String getRelativeLocationFor(String resource) {
|
||||
return ClassUtils.addResourcePathToPackagePath(getClass(), resource);
|
||||
}
|
||||
|
@ -293,4 +328,99 @@ class DataSourceInitializerInvokerTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = true)
|
||||
static class DataSourceProxyConfiguration {
|
||||
|
||||
@Bean
|
||||
static BeanPostProcessor dataSourceProxy() {
|
||||
return new BeanPostProcessor() {
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof DataSource) {
|
||||
return new DataSourceProxy((DataSource) bean);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class DataSourceProxy implements DataSource {
|
||||
|
||||
private final AtomicInteger connectionsRetrieved = new AtomicInteger();
|
||||
|
||||
private final DataSource delegate;
|
||||
|
||||
DataSourceProxy(DataSource delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getLogWriter() throws SQLException {
|
||||
return this.delegate.getLogWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogWriter(PrintWriter out) throws SQLException {
|
||||
this.delegate.setLogWriter(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWrapperFor(Class<?> iface) throws SQLException {
|
||||
return this.delegate.isWrapperFor(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T unwrap(Class<T> iface) throws SQLException {
|
||||
return this.delegate.unwrap(iface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
this.connectionsRetrieved.incrementAndGet();
|
||||
return this.delegate.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Connection getConnection(String username, String password) throws SQLException {
|
||||
this.connectionsRetrieved.incrementAndGet();
|
||||
return this.delegate.getConnection(username, password);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLoginTimeout() throws SQLException {
|
||||
return this.delegate.getLoginTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLoginTimeout(int seconds) throws SQLException {
|
||||
this.delegate.setLoginTimeout(seconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
|
||||
return this.delegate.getParentLogger();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ScopedDataSourceConfiguration {
|
||||
|
||||
@Bean
|
||||
static BeanFactoryPostProcessor fooScope() {
|
||||
return (beanFactory) -> beanFactory.registerScope("test", new SimpleThreadScope());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Scope("test")
|
||||
HikariDataSource dataSource(DataSourceProperties properties) {
|
||||
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -33,6 +33,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
|
@ -45,11 +46,9 @@ class DataSourceInitializerTests {
|
|||
@Test
|
||||
void initializeEmbeddedByDefault() {
|
||||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties());
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties(), null);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
assertThat(initializer.createSchema()).isTrue();
|
||||
assertNumberOfRows(jdbcTemplate, 0);
|
||||
initializer.initSchema();
|
||||
initializer.initializeDataSource();
|
||||
assertNumberOfRows(jdbcTemplate, 1);
|
||||
}
|
||||
}
|
||||
|
@ -59,11 +58,9 @@ class DataSourceInitializerTests {
|
|||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceProperties properties = new DataSourceProperties();
|
||||
properties.setInitializationMode(DataSourceInitializationMode.ALWAYS);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties, null);
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
assertThat(initializer.createSchema()).isTrue();
|
||||
assertNumberOfRows(jdbcTemplate, 0);
|
||||
initializer.initSchema();
|
||||
initializer.initializeDataSource();
|
||||
assertNumberOfRows(jdbcTemplate, 1);
|
||||
}
|
||||
}
|
||||
|
@ -77,8 +74,8 @@ class DataSourceInitializerTests {
|
|||
try (HikariDataSource dataSource = createDataSource()) {
|
||||
DataSourceProperties properties = new DataSourceProperties();
|
||||
properties.setInitializationMode(DataSourceInitializationMode.NEVER);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties);
|
||||
assertThat(initializer.createSchema()).isFalse();
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, properties, null);
|
||||
assertThat(initializer.initializeDataSource()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,9 +87,9 @@ class DataSourceInitializerTests {
|
|||
given(connection.getMetaData()).willReturn(metadata);
|
||||
DataSource dataSource = mock(DataSource.class);
|
||||
given(dataSource.getConnection()).willReturn(connection);
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties());
|
||||
assertThat(initializer.createSchema()).isFalse();
|
||||
verify(dataSource).getConnection();
|
||||
DataSourceInitializer initializer = new DataSourceInitializer(dataSource, new DataSourceProperties(), null);
|
||||
assertThat(initializer.initializeDataSource()).isFalse();
|
||||
verify(dataSource, times(2)).getConnection();
|
||||
}
|
||||
|
||||
private HikariDataSource createDataSource() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2020 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,7 +19,6 @@ package org.springframework.boot.autoconfigure.orm.jpa;
|
|||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -28,7 +27,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
|
@ -39,7 +37,6 @@ import javax.transaction.TransactionManager;
|
|||
import javax.transaction.UserTransaction;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.hibernate.boot.model.naming.ImplicitNamingStrategy;
|
||||
import org.hibernate.boot.model.naming.PhysicalNamingStrategy;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
|
@ -55,14 +52,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
|
||||
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceSchemaCreatedEvent;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfigurationTests.JpaUsingApplicationListenerConfiguration.EventCapturingApplicationListener;
|
||||
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.logging.LogLevel;
|
||||
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
|
||||
import org.springframework.boot.orm.jpa.hibernate.SpringJtaPlatform;
|
||||
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
|
||||
|
@ -72,6 +70,7 @@ import org.springframework.context.ApplicationEvent;
|
|||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.JpaVendorAdapter;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
|
@ -79,8 +78,8 @@ import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
|||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
|
@ -124,7 +123,8 @@ 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.data:classpath:/city.sql",
|
||||
"spring.datasource.initialization-order=after-jpa")
|
||||
.run((context) -> assertThat(context.getBean(TestInitializedJpaConfiguration.class).called).isTrue());
|
||||
}
|
||||
|
||||
|
@ -286,7 +286,8 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
|||
void customResourceMapping() {
|
||||
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.jpa.mapping-resources=META-INF/mappings/non-annotated.xml",
|
||||
"spring.datasource.initialization-order=after-jpa")
|
||||
.run((context) -> {
|
||||
EntityManager em = context.getBean(EntityManagerFactory.class).createEntityManager();
|
||||
NonAnnotatedEntity found = em.find(NonAnnotatedEntity.class, 2000L);
|
||||
|
@ -355,9 +356,12 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
|||
|
||||
@Test
|
||||
void eventListenerCanBeRegisteredAsBeans() {
|
||||
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")
|
||||
contextRunner().withInitializer(new ConditionEvaluationReportLoggingListener(LogLevel.INFO))
|
||||
.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")
|
||||
.run((context) -> {
|
||||
// See CityListener
|
||||
assertThat(context).hasSingleBean(City.class);
|
||||
|
@ -376,8 +380,10 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
|||
contextRunner().withUserConfiguration(JpaUsingApplicationListenerConfiguration.class)
|
||||
.withPropertyValues("spring.datasource.initialization-mode=never").run((context) -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getBean(EventCapturingApplicationListener.class).events.stream()
|
||||
.filter(DataSourceSchemaCreatedEvent.class::isInstance)).hasSize(1);
|
||||
EventCapturingApplicationListener listener = context
|
||||
.getBean(EventCapturingApplicationListener.class);
|
||||
assertThat(listener.events).hasSize(1);
|
||||
assertThat(listener.events).hasOnlyElementsOfType(ContextRefreshedEvent.class);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -390,8 +396,11 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
|||
assertThat(context).hasNotFailed();
|
||||
EventCapturingApplicationListener listener = context
|
||||
.getBean(EventCapturingApplicationListener.class);
|
||||
Awaitility.waitAtMost(Duration.ofSeconds(30))
|
||||
.until(() -> dataSourceSchemaCreatedEventsReceivedBy(listener), hasSize(1));
|
||||
assertThat(listener.events).hasSize(1);
|
||||
assertThat(listener.events).hasOnlyElementsOfType(ContextRefreshedEvent.class);
|
||||
// createEntityManager requires Hibernate bootstrapping to be complete
|
||||
assertThatNoException()
|
||||
.isThrownBy(() -> context.getBean(EntityManagerFactory.class).createEntityManager());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -407,11 +416,6 @@ class HibernateJpaAutoConfigurationTests extends AbstractJpaAutoConfigurationTes
|
|||
});
|
||||
}
|
||||
|
||||
private List<ApplicationEvent> dataSourceSchemaCreatedEventsReceivedBy(EventCapturingApplicationListener listener) {
|
||||
return listener.events.stream().filter(DataSourceSchemaCreatedEvent.class::isInstance)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@TestAutoConfigurationPackage(City.class)
|
||||
static class TestInitializedJpaConfiguration {
|
||||
|
|
|
@ -2029,6 +2029,11 @@ 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`.
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
spring.test.mockmvc.print=none
|
||||
spring.datasource.initialization-order=after-jpa
|
||||
|
|
Loading…
Reference in New Issue