Skip to content

Commit 8ba45cc

Browse files
committed
Allow DB migrations without DataSourceProperties
Update `FlywayAutoConfiguration`, `LiquibaseAutoConfiguration` and `DataSourceInitializer` classes so that they no longer depend on `DataSourceProperties`. DB migrations can now be performed against a `@Bean` defined primary `DataSource` with an alternative username/password. This update also removed using fallback properties when a custom connection `url` is defined with Flyway or Liquibase. We now assume that `username`, `password` and `driver-class-name` will be provided if the default values are unacceptable. Our previous logic was particularly flawed if a custom URL caused a change of driver type. Closes gh-25643
1 parent 85f1e2c commit 8ba45cc

File tree

12 files changed

+332
-141
lines changed

12 files changed

+332
-141
lines changed

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

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import java.util.Set;
26-
import java.util.function.Supplier;
2726
import java.util.stream.Collectors;
2827

2928
import javax.sql.DataSource;
@@ -44,12 +43,12 @@
4443
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4544
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayDataSourceCondition;
4645
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
47-
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
4846
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
4947
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
5048
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
5149
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5250
import org.springframework.boot.context.properties.PropertyMapper;
51+
import org.springframework.boot.jdbc.DataSourceBuilder;
5352
import org.springframework.boot.jdbc.DatabaseDriver;
5453
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
5554
import org.springframework.context.annotation.Bean;
@@ -59,8 +58,10 @@
5958
import org.springframework.core.convert.TypeDescriptor;
6059
import org.springframework.core.convert.converter.GenericConverter;
6160
import org.springframework.core.io.ResourceLoader;
61+
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
6262
import org.springframework.jdbc.support.JdbcUtils;
6363
import org.springframework.jdbc.support.MetaDataAccessException;
64+
import org.springframework.util.Assert;
6465
import org.springframework.util.CollectionUtils;
6566
import org.springframework.util.ObjectUtils;
6667
import org.springframework.util.StringUtils;
@@ -103,19 +104,17 @@ public FlywaySchemaManagementProvider flywayDefaultDdlModeProvider(ObjectProvide
103104

104105
@Configuration(proxyBeanMethods = false)
105106
@ConditionalOnMissingBean(Flyway.class)
106-
@EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
107+
@EnableConfigurationProperties(FlywayProperties.class)
107108
public static class FlywayConfiguration {
108109

109110
@Bean
110-
public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
111-
ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
112-
@FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
111+
public Flyway flyway(FlywayProperties properties, ResourceLoader resourceLoader,
112+
ObjectProvider<DataSource> dataSource, @FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
113113
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
114114
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
115115
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
116-
DataSource dataSourceToMigrate = configureDataSource(configuration, properties, dataSourceProperties,
117-
flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
118-
checkLocationExists(dataSourceToMigrate, properties, resourceLoader);
116+
configureDataSource(configuration, properties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
117+
checkLocationExists(configuration.getDataSource(), properties, resourceLoader);
119118
configureProperties(configuration, properties);
120119
List<Callback> orderedCallbacks = callbacks.orderedStream().collect(Collectors.toList());
121120
configureCallbacks(configuration, orderedCallbacks);
@@ -126,21 +125,39 @@ public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourc
126125
return configuration.load();
127126
}
128127

129-
private DataSource configureDataSource(FluentConfiguration configuration, FlywayProperties properties,
130-
DataSourceProperties dataSourceProperties, DataSource flywayDataSource, DataSource dataSource) {
131-
if (properties.isCreateDataSource()) {
132-
String url = getProperty(properties::getUrl, dataSourceProperties::determineUrl);
133-
String user = getProperty(properties::getUser, dataSourceProperties::determineUsername);
134-
String password = getProperty(properties::getPassword, dataSourceProperties::determinePassword);
135-
configuration.dataSource(url, user, password);
128+
private void configureDataSource(FluentConfiguration configuration, FlywayProperties properties,
129+
DataSource flywayDataSource, DataSource dataSource) {
130+
DataSource migrationDataSource = getMigrationDataSource(properties, flywayDataSource, dataSource);
131+
configuration.dataSource(migrationDataSource);
132+
}
133+
134+
private DataSource getMigrationDataSource(FlywayProperties properties, DataSource flywayDataSource,
135+
DataSource dataSource) {
136+
if (flywayDataSource != null) {
137+
return flywayDataSource;
136138
}
137-
else if (flywayDataSource != null) {
138-
configuration.dataSource(flywayDataSource);
139+
if (properties.getUrl() != null) {
140+
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
141+
builder.url(properties.getUrl());
142+
applyCommonBuilderProperties(properties, builder);
143+
return builder.build();
139144
}
140-
else {
141-
configuration.dataSource(dataSource);
145+
if (properties.getUser() != null && dataSource != null) {
146+
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
147+
.type(SimpleDriverDataSource.class);
148+
applyCommonBuilderProperties(properties, builder);
149+
return builder.build();
150+
}
151+
Assert.state(dataSource != null, "Flyway migration DataSource missing");
152+
return dataSource;
153+
}
154+
155+
private void applyCommonBuilderProperties(FlywayProperties properties, DataSourceBuilder<?> builder) {
156+
builder.username(properties.getUser());
157+
builder.password(properties.getPassword());
158+
if (StringUtils.hasText(properties.getDriverClassName())) {
159+
builder.driverClassName(properties.getDriverClassName());
142160
}
143-
return configuration.getDataSource();
144161
}
145162

146163
@SuppressWarnings("deprecation")
@@ -279,11 +296,6 @@ private void configureJavaMigrations(FluentConfiguration flyway, List<JavaMigrat
279296
}
280297
}
281298

282-
private String getProperty(Supplier<String> property, Supplier<String> defaultValue) {
283-
String value = property.get();
284-
return (value != null) ? value : defaultValue.get();
285-
}
286-
287299
private boolean hasAtLeastOneLocation(ResourceLoader resourceLoader, Collection<String> locations) {
288300
for (String location : locations) {
289301
if (resourceLoader.getResource(normalizePrefix(location)).exists()) {

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

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,6 @@ public class FlywayProperties {
161161
*/
162162
private String target;
163163

164-
/**
165-
* JDBC url of the database to migrate. If not set, the primary configured data source
166-
* is used.
167-
*/
168-
private String url;
169-
170164
/**
171165
* Login user of the database to migrate.
172166
*/
@@ -177,6 +171,17 @@ public class FlywayProperties {
177171
*/
178172
private String password;
179173

174+
/**
175+
* Fully qualified name of the JDBC driver. Auto-detected based on the URL by default.
176+
*/
177+
private String driverClassName;
178+
179+
/**
180+
* JDBC url of the database to migrate. If not set, the primary configured data source
181+
* is used.
182+
*/
183+
private String url;
184+
180185
/**
181186
* SQL statements to execute to initialize a connection immediately after obtaining
182187
* it.
@@ -538,18 +543,16 @@ public void setTarget(String target) {
538543
this.target = target;
539544
}
540545

546+
/**
547+
* Return if a new datasource is being created.
548+
* @return {@code true} if a new datasource is created
549+
* @deprecated since 2.5.0 in favor of directly checking user and url.
550+
*/
551+
@Deprecated
541552
public boolean isCreateDataSource() {
542553
return this.url != null || this.user != null;
543554
}
544555

545-
public String getUrl() {
546-
return this.url;
547-
}
548-
549-
public void setUrl(String url) {
550-
this.url = url;
551-
}
552-
553556
public String getUser() {
554557
return this.user;
555558
}
@@ -566,6 +569,22 @@ public void setPassword(String password) {
566569
this.password = password;
567570
}
568571

572+
public String getDriverClassName() {
573+
return this.driverClassName;
574+
}
575+
576+
public void setDriverClassName(String driverClassName) {
577+
this.driverClassName = driverClassName;
578+
}
579+
580+
public String getUrl() {
581+
return this.url;
582+
}
583+
584+
public void setUrl(String url) {
585+
this.url = url;
586+
}
587+
569588
public List<String> getInitSqls() {
570589
return this.initSqls;
571590
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.core.io.Resource;
3434
import org.springframework.core.io.ResourceLoader;
3535
import org.springframework.jdbc.config.SortedResourcesFactoryBean;
36+
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
3637
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
3738
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
3839
import org.springframework.util.StringUtils;
@@ -185,10 +186,9 @@ private void runScripts(List<Resource> resources, String username, String passwo
185186
populator.addScript(resource);
186187
}
187188
DataSource dataSource = this.dataSource;
188-
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
189-
dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
190-
.driverClassName(this.properties.determineDriverClassName()).url(this.properties.determineUrl())
191-
.username(username).password(password).build();
189+
if (StringUtils.hasText(username) && dataSource != null) {
190+
dataSource = DataSourceBuilder.derivedFrom(dataSource).type(SimpleDriverDataSource.class).username(username)
191+
.password(password).build();
192192
}
193193
DatabasePopulatorUtils.execute(populator, dataSource);
194194
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

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

19-
import java.util.function.Supplier;
20-
2119
import javax.sql.DataSource;
2220

2321
import liquibase.change.DatabaseChange;
@@ -32,18 +30,17 @@
3230
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3331
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3432
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
35-
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
3633
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseDataSourceCondition;
3734
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
3835
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3936
import org.springframework.boot.jdbc.DataSourceBuilder;
40-
import org.springframework.boot.jdbc.DatabaseDriver;
4137
import org.springframework.boot.jdbc.init.DataSourceInitializationDependencyConfigurer;
4238
import org.springframework.context.annotation.Bean;
4339
import org.springframework.context.annotation.Conditional;
4440
import org.springframework.context.annotation.Configuration;
4541
import org.springframework.context.annotation.Import;
4642
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
43+
import org.springframework.util.Assert;
4744
import org.springframework.util.StringUtils;
4845

4946
/**
@@ -77,7 +74,7 @@ public LiquibaseSchemaManagementProvider liquibaseDefaultDdlModeProvider(
7774

7875
@Configuration(proxyBeanMethods = false)
7976
@ConditionalOnMissingBean(SpringLiquibase.class)
80-
@EnableConfigurationProperties({ DataSourceProperties.class, LiquibaseProperties.class })
77+
@EnableConfigurationProperties(LiquibaseProperties.class)
8178
public static class LiquibaseConfiguration {
8279

8380
private final LiquibaseProperties properties;
@@ -87,11 +84,10 @@ public LiquibaseConfiguration(LiquibaseProperties properties) {
8784
}
8885

8986
@Bean
90-
public SpringLiquibase liquibase(DataSourceProperties dataSourceProperties,
91-
ObjectProvider<DataSource> dataSource,
87+
public SpringLiquibase liquibase(ObjectProvider<DataSource> dataSource,
9288
@LiquibaseDataSource ObjectProvider<DataSource> liquibaseDataSource) {
9389
SpringLiquibase liquibase = createSpringLiquibase(liquibaseDataSource.getIfAvailable(),
94-
dataSource.getIfUnique(), dataSourceProperties);
90+
dataSource.getIfUnique());
9591
liquibase.setChangeLog(this.properties.getChangeLog());
9692
liquibase.setClearCheckSums(this.properties.isClearChecksums());
9793
liquibase.setContexts(this.properties.getContexts());
@@ -110,51 +106,43 @@ public SpringLiquibase liquibase(DataSourceProperties dataSourceProperties,
110106
return liquibase;
111107
}
112108

113-
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDatasource, DataSource dataSource,
114-
DataSourceProperties dataSourceProperties) {
115-
DataSource liquibaseDataSource = getDataSource(liquibaseDatasource, dataSource);
116-
if (liquibaseDataSource != null) {
117-
SpringLiquibase liquibase = new SpringLiquibase();
118-
liquibase.setDataSource(liquibaseDataSource);
119-
return liquibase;
120-
}
121-
SpringLiquibase liquibase = new DataSourceClosingSpringLiquibase();
122-
liquibase.setDataSource(createNewDataSource(dataSourceProperties));
109+
private SpringLiquibase createSpringLiquibase(DataSource liquibaseDataSource, DataSource dataSource) {
110+
LiquibaseProperties properties = this.properties;
111+
DataSource migrationDataSource = getMigrationDataSource(liquibaseDataSource, dataSource, properties);
112+
SpringLiquibase liquibase = (migrationDataSource == liquibaseDataSource
113+
|| migrationDataSource == dataSource) ? new SpringLiquibase()
114+
: new DataSourceClosingSpringLiquibase();
115+
liquibase.setDataSource(migrationDataSource);
123116
return liquibase;
124117
}
125118

126-
private DataSource getDataSource(DataSource liquibaseDataSource, DataSource dataSource) {
119+
private DataSource getMigrationDataSource(DataSource liquibaseDataSource, DataSource dataSource,
120+
LiquibaseProperties properties) {
127121
if (liquibaseDataSource != null) {
128122
return liquibaseDataSource;
129123
}
130-
if (this.properties.getUrl() == null && this.properties.getUser() == null) {
131-
return dataSource;
124+
if (properties.getUrl() != null) {
125+
DataSourceBuilder<?> builder = DataSourceBuilder.create().type(SimpleDriverDataSource.class);
126+
builder.url(properties.getUrl());
127+
applyCommonBuilderProperties(properties, builder);
128+
return builder.build();
132129
}
133-
return null;
134-
}
135-
136-
private DataSource createNewDataSource(DataSourceProperties dataSourceProperties) {
137-
String url = getProperty(this.properties::getUrl, dataSourceProperties::determineUrl);
138-
String user = getProperty(this.properties::getUser, dataSourceProperties::determineUsername);
139-
String password = getProperty(this.properties::getPassword, dataSourceProperties::determinePassword);
140-
String driverClassName = determineDriverClassName(dataSourceProperties, url);
141-
return DataSourceBuilder.create().type(SimpleDriverDataSource.class).url(url).username(user)
142-
.password(password).driverClassName(driverClassName).build();
143-
}
144-
145-
private String determineDriverClassName(DataSourceProperties dataSourceProperties, String url) {
146-
if (StringUtils.hasText(this.properties.getDriverClassName())) {
147-
return this.properties.getDriverClassName();
130+
if (properties.getUser() != null && dataSource != null) {
131+
DataSourceBuilder<?> builder = DataSourceBuilder.derivedFrom(dataSource)
132+
.type(SimpleDriverDataSource.class);
133+
applyCommonBuilderProperties(properties, builder);
134+
return builder.build();
148135
}
149-
if (StringUtils.hasText(dataSourceProperties.getDriverClassName())) {
150-
return dataSourceProperties.getDriverClassName();
151-
}
152-
return StringUtils.hasText(url) ? DatabaseDriver.fromJdbcUrl(url).getDriverClassName() : null;
136+
Assert.state(dataSource != null, "Liquibase migration DataSource missing");
137+
return dataSource;
153138
}
154139

155-
private String getProperty(Supplier<String> property, Supplier<String> defaultValue) {
156-
String value = property.get();
157-
return (value != null) ? value : defaultValue.get();
140+
private void applyCommonBuilderProperties(LiquibaseProperties properties, DataSourceBuilder<?> builder) {
141+
builder.username(properties.getUser());
142+
builder.password(properties.getPassword());
143+
if (StringUtils.hasText(properties.getDriverClassName())) {
144+
builder.driverClassName(properties.getDriverClassName());
145+
}
158146
}
159147

160148
}

0 commit comments

Comments
 (0)