Skip to content

Commit 2f83a67

Browse files
committed
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
1 parent b3d73a1 commit 2f83a67

19 files changed

+539
-538
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2012-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import javax.sql.DataSource;
20+
21+
import org.springframework.beans.factory.InitializingBean;
22+
import org.springframework.context.ResourceLoaderAware;
23+
import org.springframework.core.io.ResourceLoader;
24+
25+
/**
26+
* {@link InitializingBean} that performs {@link DataSource} initialization using DDL and
27+
* DML scripts.
28+
*
29+
* @author Andy Wilkinson
30+
* @since 2.5.0
31+
*/
32+
public class DataSourceInitialization implements InitializingBean, ResourceLoaderAware {
33+
34+
private final DataSource dataSource;
35+
36+
private final DataSourceProperties properies;
37+
38+
private volatile ResourceLoader resourceLoader;
39+
40+
/**
41+
* Creates a new {@link DataSourceInitialization} that will initialize the given
42+
* {@code DataSource} using the settings from the given {@code properties}.
43+
* @param dataSource the DataSource to initialize
44+
* @param properies the properties containing the initialization settings
45+
*/
46+
public DataSourceInitialization(DataSource dataSource, DataSourceProperties properies) {
47+
this.dataSource = dataSource;
48+
this.properies = properies;
49+
}
50+
51+
@Override
52+
public void afterPropertiesSet() throws Exception {
53+
new DataSourceInitializer(this.dataSource, this.properies, this.resourceLoader).initializeDataSource();
54+
}
55+
56+
@Override
57+
public void setResourceLoader(ResourceLoader resourceLoader) {
58+
this.resourceLoader = resourceLoader;
59+
}
60+
61+
}

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializationConfiguration.java

+66-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,47 +16,82 @@
1616

1717
package org.springframework.boot.autoconfigure.jdbc;
1818

19-
import org.springframework.beans.factory.config.BeanDefinition;
20-
import org.springframework.beans.factory.support.AbstractBeanDefinition;
21-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
22-
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
19+
import javax.persistence.EntityManagerFactory;
20+
import javax.sql.DataSource;
21+
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
25+
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
26+
import org.springframework.context.annotation.Bean;
2327
import org.springframework.context.annotation.Configuration;
2428
import org.springframework.context.annotation.Import;
25-
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
26-
import org.springframework.core.type.AnnotationMetadata;
29+
import org.springframework.jdbc.core.JdbcOperations;
30+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
31+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
2732

2833
/**
29-
* Configures DataSource initialization.
34+
* Configuration for {@link DataSource} initialization using DDL and DML scripts.
3035
*
31-
* @author Stephane Nicoll
36+
* @author Andy Wilkinson
3237
*/
3338
@Configuration(proxyBeanMethods = false)
34-
@Import({ DataSourceInitializerInvoker.class, DataSourceInitializationConfiguration.Registrar.class })
39+
@ConditionalOnSingleCandidate(DataSource.class)
3540
class DataSourceInitializationConfiguration {
3641

42+
@Configuration(proxyBeanMethods = false)
43+
@ConditionalOnProperty(prefix = "spring.datasource", name = "initialization-order", havingValue = "before-jpa",
44+
matchIfMissing = true)
45+
@Import({ DataSourceInitializationJdbcOperationsDependsOnPostProcessor.class,
46+
DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor.class,
47+
DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor.class })
48+
static class BeforeJpaDataSourceInitializationConfiguration {
49+
50+
@Bean
51+
DataSourceInitialization dataSourceInitialization(DataSource dataSource, DataSourceProperties properties) {
52+
return new DataSourceInitialization(dataSource, properties);
53+
}
54+
55+
}
56+
3757
/**
38-
* {@link ImportBeanDefinitionRegistrar} to register the
39-
* {@link DataSourceInitializerPostProcessor} without causing early bean instantiation
40-
* issues.
58+
* Post processor to ensure that {@link EntityManagerFactory} beans depend on any
59+
* {@link DataSourceInitialization} beans.
4160
*/
42-
static class Registrar implements ImportBeanDefinitionRegistrar {
43-
44-
private static final String BEAN_NAME = "dataSourceInitializerPostProcessor";
45-
46-
@Override
47-
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
48-
BeanDefinitionRegistry registry) {
49-
if (!registry.containsBeanDefinition(BEAN_NAME)) {
50-
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
51-
.genericBeanDefinition(DataSourceInitializerPostProcessor.class,
52-
DataSourceInitializerPostProcessor::new)
53-
.getBeanDefinition();
54-
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
55-
// We don't need this one to be post processed otherwise it can cause a
56-
// cascade of bean instantiation that we would rather avoid.
57-
beanDefinition.setSynthetic(true);
58-
registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
59-
}
61+
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
62+
static class DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor
63+
extends EntityManagerFactoryDependsOnPostProcessor {
64+
65+
DataSourceInitializationEntityManagerFactoryDependsOnPostProcessor() {
66+
super(DataSourceInitialization.class);
67+
}
68+
69+
}
70+
71+
/**
72+
* Post processor to ensure that {@link JdbcOperations} beans depend on any
73+
* {@link DataSourceInitialization} beans.
74+
*/
75+
@ConditionalOnClass(JdbcOperations.class)
76+
static class DataSourceInitializationJdbcOperationsDependsOnPostProcessor
77+
extends JdbcOperationsDependsOnPostProcessor {
78+
79+
DataSourceInitializationJdbcOperationsDependsOnPostProcessor() {
80+
super(DataSourceInitialization.class);
81+
}
82+
83+
}
84+
85+
/**
86+
* Post processor to ensure that {@link NamedParameterJdbcOperations} beans depend on
87+
* any {@link DataSourceInitialization} beans.
88+
*/
89+
@ConditionalOnClass(NamedParameterJdbcOperations.class)
90+
protected static class DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor
91+
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
92+
93+
public DataSourceInitializationNamedParameterJdbcOperationsDependsOnPostProcessor() {
94+
super(DataSourceInitialization.class);
6095
}
6196

6297
}

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java

+16-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -46,8 +46,9 @@
4646
* @author Eddú Meléndez
4747
* @author Stephane Nicoll
4848
* @author Kazuki Shimizu
49+
* @since 2.5.0
4950
*/
50-
class DataSourceInitializer {
51+
public class DataSourceInitializer {
5152

5253
private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);
5354

@@ -64,32 +65,25 @@ class DataSourceInitializer {
6465
* @param properties the matching configuration
6566
* @param resourceLoader the resource loader to use (can be null)
6667
*/
67-
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties, ResourceLoader resourceLoader) {
68+
public DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
69+
ResourceLoader resourceLoader) {
6870
this.dataSource = dataSource;
6971
this.properties = properties;
7072
this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(null);
7173
}
7274

7375
/**
74-
* Create a new instance with the {@link DataSource} to initialize and its matching
75-
* {@link DataSourceProperties configuration}.
76-
* @param dataSource the datasource to initialize
77-
* @param properties the matching configuration
76+
* Initializes the {@link DataSource} by running DDL and DML scripts.
77+
* @return {@code true} if one or more scripts were applied to the database, otherwise
78+
* {@code false}
7879
*/
79-
DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
80-
this(dataSource, properties, null);
81-
}
82-
83-
DataSource getDataSource() {
84-
return this.dataSource;
80+
public boolean initializeDataSource() {
81+
boolean initialized = createSchema();
82+
initialized = initSchema() && initialized;
83+
return initialized;
8584
}
8685

87-
/**
88-
* Create the schema if necessary.
89-
* @return {@code true} if the schema was created
90-
* @see DataSourceProperties#getSchema()
91-
*/
92-
boolean createSchema() {
86+
private boolean createSchema() {
9387
List<Resource> scripts = getScripts("spring.datasource.schema", this.properties.getSchema(), "schema");
9488
if (!scripts.isEmpty()) {
9589
if (!isEnabled()) {
@@ -103,21 +97,18 @@ boolean createSchema() {
10397
return !scripts.isEmpty();
10498
}
10599

106-
/**
107-
* Initialize the schema if necessary.
108-
* @see DataSourceProperties#getData()
109-
*/
110-
void initSchema() {
100+
private boolean initSchema() {
111101
List<Resource> scripts = getScripts("spring.datasource.data", this.properties.getData(), "data");
112102
if (!scripts.isEmpty()) {
113103
if (!isEnabled()) {
114104
logger.debug("Initialization disabled (not running data scripts)");
115-
return;
105+
return false;
116106
}
117107
String username = this.properties.getDataUsername();
118108
String password = this.properties.getDataPassword();
119109
runScripts(scripts, username, password);
120110
}
111+
return !scripts.isEmpty();
121112
}
122113

123114
private boolean isEnabled() {

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerInvoker.java

-106
This file was deleted.

0 commit comments

Comments
 (0)