Skip to content

Commit d09ac00

Browse files
wilkinsonamhalbritterphilwebb
committed
Add ConnectionDetail support to JDBC auto-configuration
Update JDBC auto-configuration so that `JdbcConnectionDetails` beans may be optionally used to provide connection details. See gh-34657 Co-Authored-By: Mortitz Halbritter <[email protected]> Co-Authored-By: Phillip Webb <[email protected]>
1 parent aa91f2b commit d09ac00

23 files changed

+1198
-102
lines changed

spring-boot-project/spring-boot-autoconfigure/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ dependencies {
236236
testImplementation("org.junit.jupiter:junit-jupiter")
237237
testImplementation("org.mockito:mockito-core")
238238
testImplementation("org.mockito:mockito-junit-jupiter")
239+
testImplementation("org.postgresql:postgresql")
239240
testImplementation("org.skyscreamer:jsonassert")
240241
testImplementation("org.springframework:spring-test")
241242
testImplementation("org.springframework:spring-core-test")

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayAutoConfigurationRuntimeHints;
4848
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
4949
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
50+
import org.springframework.boot.autoconfigure.jdbc.JdbcConnectionDetails;
5051
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
5152
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
5253
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
@@ -86,6 +87,7 @@
8687
* @author Semyon Danilov
8788
* @author Chris Bono
8889
* @author Moritz Halbritter
90+
* @author Andy Wilkinson
8991
* @since 1.1.0
9092
*/
9193
@AutoConfiguration(after = { DataSourceAutoConfiguration.class, JdbcTemplateAutoConfiguration.class,
@@ -125,17 +127,23 @@ public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
125127
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
126128
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
127129
return flyway(properties, resourceLoader, dataSource, flywayDataSource, fluentConfigurationCustomizers,
128-
javaMigrations, callbacks, new ResourceProviderCustomizer());
130+
javaMigrations, callbacks, new ResourceProviderCustomizer(), null);
129131
}
130132

131133
@Bean
132134
Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
133135
@FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
134136
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
135137
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks,
136-
ResourceProviderCustomizer resourceProviderCustomizer) {
138+
ResourceProviderCustomizer resourceProviderCustomizer,
139+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
137140
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
138-
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
141+
JdbcConnectionDetails connectionDetails = (connectionDetailsProvider != null)
142+
? connectionDetailsProvider.getIfAvailable() : null;
143+
connectionDetails = (connectionDetails != null) ? connectionDetails
144+
: new FlywayPropertiesJdbcConnectionDetails(properties);
145+
configureDataSource(configuration, flywayDataSource.getIfAvailable(), dataSource.getIfUnique(),
146+
connectionDetails);
139147
configureProperties(configuration, properties);
140148
configureCallbacks(configuration, callbacks.orderedStream().toList());
141149
configureJavaMigrations(configuration, javaMigrations.orderedStream().toList());
@@ -144,38 +152,41 @@ Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader, Object
144152
return configuration.load();
145153
}
146154

147-
private void configureDataSource(FluentConfiguration configuration, FlywayProperties properties,
148-
DataSource flywayDataSource, DataSource dataSource) {
149-
DataSource migrationDataSource = getMigrationDataSource(properties, flywayDataSource, dataSource);
155+
private void configureDataSource(FluentConfiguration configuration, DataSource flywayDataSource,
156+
DataSource dataSource, JdbcConnectionDetails connectionDetails) {
157+
DataSource migrationDataSource = getMigrationDataSource(flywayDataSource, dataSource, connectionDetails);
150158
configuration.dataSource(migrationDataSource);
151159
}
152160

153-
private DataSource getMigrationDataSource(FlywayProperties properties, DataSource flywayDataSource,
154-
DataSource dataSource) {
161+
private DataSource getMigrationDataSource(DataSource flywayDataSource, DataSource dataSource,
162+
JdbcConnectionDetails connectionDetails) {
155163
if (flywayDataSource != null) {
156164
return flywayDataSource;
157165
}
158-
if (properties.getUrl() != null) {
166+
String url = connectionDetails.getJdbcUrl();
167+
if (url != null) {
159168
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
160-
builder.url(properties.getUrl());
161-
applyCommonBuilderProperties(properties, builder);
169+
builder.url(url);
170+
applyConnectionDetails(connectionDetails, builder);
162171
return builder.build();
163172
}
164-
if (properties.getUser() != null && dataSource != null) {
173+
String user = connectionDetails.getUsername();
174+
if (user != null && dataSource != null) {
165175
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
166176
.type(SimpleDriverDataSource.class);
167-
applyCommonBuilderProperties(properties, builder);
177+
applyConnectionDetails(connectionDetails, builder);
168178
return builder.build();
169179
}
170180
Assert.state(dataSource != null, "Flyway migration DataSource missing");
171181
return dataSource;
172182
}
173183

174-
private void applyCommonBuilderProperties(FlywayProperties properties, DataSourceBuilder<?> builder) {
175-
builder.username(properties.getUser());
176-
builder.password(properties.getPassword());
177-
if (StringUtils.hasText(properties.getDriverClassName())) {
178-
builder.driverClassName(properties.getDriverClassName());
184+
private void applyConnectionDetails(JdbcConnectionDetails connectionDetails, DataSourceBuilder<?> builder) {
185+
builder.username(connectionDetails.getUsername());
186+
builder.password(connectionDetails.getPassword());
187+
String driverClassName = connectionDetails.getDriverClassName();
188+
if (StringUtils.hasText(driverClassName)) {
189+
builder.driverClassName(driverClassName);
179190
}
180191
}
181192

@@ -373,6 +384,11 @@ private static final class DataSourceBeanCondition {
373384

374385
}
375386

387+
@ConditionalOnBean(JdbcConnectionDetails.class)
388+
private static final class JdbcConnectionDetailsCondition {
389+
390+
}
391+
376392
@ConditionalOnProperty(prefix = "spring.flyway", name = "url")
377393
private static final class FlywayUrlCondition {
378394

@@ -389,4 +405,37 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
389405

390406
}
391407

408+
/**
409+
* Adapts {@link FlywayProperties} to {@link JdbcConnectionDetails}.
410+
*/
411+
private static final class FlywayPropertiesJdbcConnectionDetails implements JdbcConnectionDetails {
412+
413+
private final FlywayProperties properties;
414+
415+
private FlywayPropertiesJdbcConnectionDetails(FlywayProperties properties) {
416+
this.properties = properties;
417+
}
418+
419+
@Override
420+
public String getUsername() {
421+
return this.properties.getUser();
422+
}
423+
424+
@Override
425+
public String getPassword() {
426+
return this.properties.getPassword();
427+
}
428+
429+
@Override
430+
public String getJdbcUrl() {
431+
return this.properties.getUrl();
432+
}
433+
434+
@Override
435+
public String getDriverClassName() {
436+
return this.properties.getDriverClassName();
437+
}
438+
439+
}
440+
392441
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java

Lines changed: 86 additions & 13 deletions
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-2023 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.
@@ -24,10 +24,13 @@
2424
import oracle.jdbc.OracleConnection;
2525
import oracle.ucp.jdbc.PoolDataSourceImpl;
2626

27+
import org.springframework.beans.factory.ObjectProvider;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2830
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2931
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3032
import org.springframework.boot.context.properties.ConfigurationProperties;
33+
import org.springframework.boot.jdbc.DataSourceBuilder;
3134
import org.springframework.boot.jdbc.DatabaseDriver;
3235
import org.springframework.context.annotation.Bean;
3336
import org.springframework.context.annotation.Configuration;
@@ -40,6 +43,8 @@
4043
* @author Phillip Webb
4144
* @author Stephane Nicoll
4245
* @author Fabio Grassi
46+
* @author Moritz Halbritter
47+
* @author Andy Wilkinson
4348
*/
4449
abstract class DataSourceConfiguration {
4550

@@ -48,6 +53,17 @@ protected static <T> T createDataSource(DataSourceProperties properties, Class<?
4853
return (T) properties.initializeDataSourceBuilder().type(type).build();
4954
}
5055

56+
@SuppressWarnings("unchecked")
57+
protected static <T> T createDataSource(JdbcConnectionDetails connectionDetails, Class<? extends DataSource> type,
58+
ClassLoader classLoader) {
59+
return (T) DataSourceBuilder.create(classLoader)
60+
.url(connectionDetails.getJdbcUrl())
61+
.username(connectionDetails.getUsername())
62+
.password(connectionDetails.getPassword())
63+
.type(type)
64+
.build();
65+
}
66+
5167
/**
5268
* Tomcat Pool DataSource configuration.
5369
*/
@@ -58,13 +74,26 @@ protected static <T> T createDataSource(DataSourceProperties properties, Class<?
5874
matchIfMissing = true)
5975
static class Tomcat {
6076

77+
@Bean
78+
@ConditionalOnBean(JdbcConnectionDetails.class)
79+
static TomcatJdbcConnectionDetailsBeanPostProcessor tomcatJdbcConnectionDetailsBeanPostProcessor(
80+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
81+
return new TomcatJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
82+
}
83+
6184
@Bean
6285
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
63-
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties) {
64-
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(properties,
65-
org.apache.tomcat.jdbc.pool.DataSource.class);
66-
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
67-
String validationQuery = databaseDriver.getValidationQuery();
86+
org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties properties,
87+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
88+
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
89+
Class<? extends DataSource> dataSourceType = org.apache.tomcat.jdbc.pool.DataSource.class;
90+
org.apache.tomcat.jdbc.pool.DataSource dataSource = (connectionDetails != null)
91+
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
92+
: createDataSource(properties, dataSourceType);
93+
String validationQuery;
94+
String url = (connectionDetails != null) ? connectionDetails.getJdbcUrl() : properties.determineUrl();
95+
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(url);
96+
validationQuery = databaseDriver.getValidationQuery();
6897
if (validationQuery != null) {
6998
dataSource.setTestOnBorrow(true);
7099
dataSource.setValidationQuery(validationQuery);
@@ -84,10 +113,21 @@ org.apache.tomcat.jdbc.pool.DataSource dataSource(DataSourceProperties propertie
84113
matchIfMissing = true)
85114
static class Hikari {
86115

116+
@Bean
117+
@ConditionalOnBean(JdbcConnectionDetails.class)
118+
static HikariJdbcConnectionDetailsBeanPostProcessor jdbcConnectionDetailsHikariBeanPostProcessor(
119+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
120+
return new HikariJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
121+
}
122+
87123
@Bean
88124
@ConfigurationProperties(prefix = "spring.datasource.hikari")
89-
HikariDataSource dataSource(DataSourceProperties properties) {
90-
HikariDataSource dataSource = createDataSource(properties, HikariDataSource.class);
125+
HikariDataSource dataSource(DataSourceProperties properties,
126+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
127+
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
128+
HikariDataSource dataSource = (connectionDetails != null)
129+
? createDataSource(connectionDetails, HikariDataSource.class, properties.getClassLoader())
130+
: createDataSource(properties, HikariDataSource.class);
91131
if (StringUtils.hasText(properties.getName())) {
92132
dataSource.setPoolName(properties.getName());
93133
}
@@ -106,10 +146,22 @@ HikariDataSource dataSource(DataSourceProperties properties) {
106146
matchIfMissing = true)
107147
static class Dbcp2 {
108148

149+
@Bean
150+
@ConditionalOnBean(JdbcConnectionDetails.class)
151+
static Dbcp2JdbcConnectionDetailsBeanPostProcessor dbcp2JdbcConnectionDetailsBeanPostProcessor(
152+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
153+
return new Dbcp2JdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
154+
}
155+
109156
@Bean
110157
@ConfigurationProperties(prefix = "spring.datasource.dbcp2")
111-
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties) {
112-
return createDataSource(properties, org.apache.commons.dbcp2.BasicDataSource.class);
158+
org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties properties,
159+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
160+
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
161+
Class<? extends DataSource> dataSourceType = org.apache.commons.dbcp2.BasicDataSource.class;
162+
return (connectionDetails != null)
163+
? createDataSource(connectionDetails, dataSourceType, properties.getClassLoader())
164+
: createDataSource(properties, dataSourceType);
113165
}
114166

115167
}
@@ -124,10 +176,21 @@ org.apache.commons.dbcp2.BasicDataSource dataSource(DataSourceProperties propert
124176
matchIfMissing = true)
125177
static class OracleUcp {
126178

179+
@Bean
180+
@ConditionalOnBean(JdbcConnectionDetails.class)
181+
static OracleUcpJdbcConnectionDetailsBeanPostProcessor oracleUcpJdbcConnectionDetailsBeanPostProcessor(
182+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
183+
return new OracleUcpJdbcConnectionDetailsBeanPostProcessor(connectionDetailsProvider);
184+
}
185+
127186
@Bean
128187
@ConfigurationProperties(prefix = "spring.datasource.oracleucp")
129-
PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLException {
130-
PoolDataSourceImpl dataSource = createDataSource(properties, PoolDataSourceImpl.class);
188+
PoolDataSourceImpl dataSource(DataSourceProperties properties,
189+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) throws SQLException {
190+
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
191+
PoolDataSourceImpl dataSource = (connectionDetails != null)
192+
? createDataSource(connectionDetails, PoolDataSourceImpl.class, properties.getClassLoader())
193+
: createDataSource(properties, PoolDataSourceImpl.class);
131194
dataSource.setValidateConnectionOnBorrow(true);
132195
if (StringUtils.hasText(properties.getName())) {
133196
dataSource.setConnectionPoolName(properties.getName());
@@ -146,7 +209,17 @@ PoolDataSourceImpl dataSource(DataSourceProperties properties) throws SQLExcepti
146209
static class Generic {
147210

148211
@Bean
149-
DataSource dataSource(DataSourceProperties properties) {
212+
DataSource dataSource(DataSourceProperties properties,
213+
ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
214+
JdbcConnectionDetails connectionDetails = connectionDetailsProvider.getIfAvailable();
215+
if (connectionDetails != null) {
216+
return DataSourceBuilder.create(properties.getClassLoader())
217+
.url(connectionDetails.getJdbcUrl())
218+
.username(connectionDetails.getUsername())
219+
.password(connectionDetails.getPassword())
220+
.type(properties.getType())
221+
.build();
222+
}
150223
return properties.initializeDataSourceBuilder().build();
151224
}
152225

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-2023 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 org.apache.commons.dbcp2.BasicDataSource;
20+
21+
import org.springframework.beans.factory.ObjectProvider;
22+
23+
/**
24+
* Post-processes beans of type {@link BasicDataSource} and name 'dataSource' to apply the
25+
* values from {@link JdbcConnectionDetails}.
26+
*
27+
* @author Moritz Halbritter
28+
* @author Andy Wilkinson
29+
* @author Phillip Webb
30+
*/
31+
class Dbcp2JdbcConnectionDetailsBeanPostProcessor extends JdbcConnectionDetailsBeanPostProcessor<BasicDataSource> {
32+
33+
Dbcp2JdbcConnectionDetailsBeanPostProcessor(ObjectProvider<JdbcConnectionDetails> connectionDetailsProvider) {
34+
super(BasicDataSource.class, connectionDetailsProvider);
35+
}
36+
37+
@Override
38+
protected Object processDataSource(BasicDataSource dataSource, JdbcConnectionDetails connectionDetails) {
39+
dataSource.setUrl(connectionDetails.getJdbcUrl());
40+
dataSource.setUsername(connectionDetails.getUsername());
41+
dataSource.setPassword(connectionDetails.getPassword());
42+
dataSource.setDriverClassName(connectionDetails.getDriverClassName());
43+
return dataSource;
44+
}
45+
46+
}

0 commit comments

Comments
 (0)