From 5d3cd23eed088622840576167c4c484c89352353 Mon Sep 17 00:00:00 2001 From: Dominic Gunn Date: Tue, 23 Jan 2018 22:11:40 +0000 Subject: [PATCH 1/2] Use custom DataSource if Flyway or Liquibase has user or url This commit enables a more flexible Liquibase/Flyway configuration by allowing for a combination of the provider's and the primary DataSource's configuration to be used. This gives developers the flexibility to specify only a user or a url and having Liquibase/Flyway fall back to individual datasource properties rather than ignoring the Liquibase/Flyway properties and falling back to the default data source. See gh-11751 --- .../flyway/FlywayAutoConfiguration.java | 28 +++++++++++++++---- .../flyway/FlywayProperties.java | 2 +- .../liquibase/LiquibaseAutoConfiguration.java | 27 ++++++++++++++---- .../flyway/FlywayAutoConfigurationTests.java | 15 ++++++++-- .../LiquibaseAutoConfigurationTests.java | 19 +++++++++++-- 5 files changed, 75 insertions(+), 16 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index eb7c25a60cd..46a8f698d88 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationPropertiesBinding; @@ -65,6 +66,7 @@ import org.springframework.util.ObjectUtils; * @author Stephane Nicoll * @author Jacques-Etienne Beaudet * @author Eddú Meléndez + * @author Dominic Gunn * @since 1.1.0 */ @Configuration @@ -95,6 +97,8 @@ public class FlywayAutoConfiguration { private final FlywayProperties properties; + private final DataSourceProperties dataSourceProperties; + private final ResourceLoader resourceLoader; private final DataSource dataSource; @@ -106,11 +110,13 @@ public class FlywayAutoConfiguration { private List flywayCallbacks; public FlywayConfiguration(FlywayProperties properties, - ResourceLoader resourceLoader, ObjectProvider dataSource, + DataSourceProperties dataSourceProperties, ResourceLoader resourceLoader, + ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, ObjectProvider migrationStrategy, ObjectProvider> flywayCallbacks) { this.properties = properties; + this.dataSourceProperties = dataSourceProperties; this.resourceLoader = resourceLoader; this.dataSource = dataSource.getIfUnique(); this.flywayDataSource = flywayDataSource.getIfAvailable(); @@ -123,8 +129,19 @@ public class FlywayAutoConfiguration { public Flyway flyway() { Flyway flyway = new SpringBootFlyway(); if (this.properties.isCreateDataSource()) { - flyway.setDataSource(this.properties.getUrl(), this.properties.getUser(), - this.properties.getPassword(), + String url = this.properties.getUrl() == null + ? this.dataSourceProperties.getUrl() + : this.properties.getUrl(); + + String user = this.properties.getUser() == null + ? this.dataSourceProperties.getUsername() + : this.properties.getUser(); + + String password = this.properties.getPassword() == null + ? this.dataSourceProperties.getPassword() + : this.properties.getPassword(); + + flyway.setDataSource(url, user, password, this.properties.getInitSqls().toArray(new String[0])); } else if (this.flywayDataSource != null) { @@ -147,9 +164,8 @@ public class FlywayAutoConfiguration { Assert.state(locations.length != 0, "Migration script locations not configured"); boolean exists = hasAtLeastOneLocation(locations); - Assert.state(exists, - () -> "Cannot find migrations location in: " + Arrays.asList( - locations) + Assert.state(exists, () -> "Cannot find migrations location in: " + + Arrays.asList(locations) + " (please add migrations or check your Flyway configuration)"); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index 2784d669462..5eef9b8020c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -133,7 +133,7 @@ public class FlywayProperties { } public boolean isCreateDataSource() { - return this.url != null && this.user != null; + return this.url != null || this.user != null; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 0368ac10302..82fa8c741b9 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -37,6 +37,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; @@ -58,6 +59,7 @@ import org.springframework.util.ReflectionUtils; * @author Phillip Webb * @author Eddú Meléndez * @author Andy Wilkinson + * @author Dominic Gunn * @since 1.1.0 */ @Configuration @@ -83,6 +85,8 @@ public class LiquibaseAutoConfiguration { private final LiquibaseProperties properties; + private final DataSourceProperties dataSourceProperties; + private final ResourceLoader resourceLoader; private final DataSource dataSource; @@ -90,9 +94,11 @@ public class LiquibaseAutoConfiguration { private final DataSource liquibaseDataSource; public LiquibaseConfiguration(LiquibaseProperties properties, - ResourceLoader resourceLoader, ObjectProvider dataSource, + DataSourceProperties dataSourceProperties, ResourceLoader resourceLoader, + ObjectProvider dataSource, @LiquibaseDataSource ObjectProvider liquibaseDataSource) { this.properties = properties; + this.dataSourceProperties = dataSourceProperties; this.resourceLoader = resourceLoader; this.dataSource = dataSource.getIfUnique(); this.liquibaseDataSource = liquibaseDataSource.getIfAvailable(); @@ -140,16 +146,27 @@ public class LiquibaseAutoConfiguration { if (this.liquibaseDataSource != null) { return this.liquibaseDataSource; } - if (this.properties.getUrl() == null) { + if (this.properties.getUrl() == null && this.properties.getUser() == null) { return this.dataSource; } return null; } private DataSource createNewDataSource() { - return DataSourceBuilder.create().url(this.properties.getUrl()) - .username(this.properties.getUser()) - .password(this.properties.getPassword()).build(); + String url = this.properties.getUrl() == null + ? this.dataSourceProperties.getUrl() + : this.properties.getUrl(); + + String user = this.properties.getUser() == null + ? this.dataSourceProperties.getUsername() + : this.properties.getUser(); + + String password = this.properties.getPassword() == null + ? this.dataSourceProperties.getPassword() + : this.properties.getPassword(); + + return DataSourceBuilder.create().url(url).username(user).password(password) + .build(); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index fd298cfad06..a205d1fd358 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -60,6 +60,7 @@ import static org.mockito.Mockito.mock; * @author Vedran Pavic * @author Eddú Meléndez * @author Stephane Nicoll + * @author Dominic Gunn */ public class FlywayAutoConfigurationTests { @@ -74,9 +75,19 @@ public class FlywayAutoConfigurationTests { } @Test - public void createDataSource() { + public void createDataSourceWithUrl() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:flywaytest", + .withPropertyValues("spring.flyway.url:jdbc:hsqldb:mem:flywaytest") + .run((context) -> { + assertThat(context).hasSingleBean(Flyway.class); + assertThat(context.getBean(Flyway.class).getDataSource()).isNotNull(); + }); + } + + @Test + public void createDataSourceWithUser() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:normal", "spring.flyway.user:sa") .run((context) -> { assertThat(context).hasSingleBean(Flyway.class); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java index a9caa2eb813..bb31bcb138f 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfigurationTests.java @@ -58,6 +58,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Eddú Meléndez * @author Andy Wilkinson * @author Stephane Nicoll + * @author Dominic Gunn */ public class LiquibaseAutoConfigurationTests { @@ -153,8 +154,7 @@ public class LiquibaseAutoConfigurationTests { @Test public void overrideDataSource() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) - .withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase", - "spring.liquibase.user:sa") + .withPropertyValues("spring.liquibase.url:jdbc:hsqldb:mem:liquibase") .run(assertLiquibase((liquibase) -> { DataSource dataSource = liquibase.getDataSource(); assertThat(((HikariDataSource) dataSource).isClosed()).isTrue(); @@ -163,6 +163,21 @@ public class LiquibaseAutoConfigurationTests { })); } + @Test + public void overrideUser() { + this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.datasource.url:jdbc:hsqldb:mem:normal", + "spring.datasource.username:not-sa", "spring.liquibase.user:sa") + .run(assertLiquibase((liquibase) -> { + DataSource dataSource = liquibase.getDataSource(); + assertThat(((HikariDataSource) dataSource).isClosed()).isTrue(); + assertThat(((HikariDataSource) dataSource).getJdbcUrl()) + .isEqualTo("jdbc:hsqldb:mem:normal"); + assertThat(((HikariDataSource) dataSource).getUsername()) + .isEqualTo("sa"); + })); + } + @Test public void changeLogDoesNotExist() { this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class) From 192fe929c7612a7fb1c5a5de74d62d64d1a76438 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 26 Jan 2018 14:35:28 +0000 Subject: [PATCH 2/2] Polish "Use custom DataSource if Flyway or Liquibase has user or url" Closes gh-11751 --- .../flyway/FlywayAutoConfiguration.java | 32 ++++++++++--------- .../flyway/FlywayProperties.java | 2 +- .../liquibase/LiquibaseAutoConfiguration.java | 25 ++++++++------- .../flyway/FlywayAutoConfigurationTests.java | 2 +- .../src/main/asciidoc/howto.adoc | 10 ++++-- 5 files changed, 40 insertions(+), 31 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index 46a8f698d88..5ce8b0b5d52 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @@ -129,18 +130,12 @@ public class FlywayAutoConfiguration { public Flyway flyway() { Flyway flyway = new SpringBootFlyway(); if (this.properties.isCreateDataSource()) { - String url = this.properties.getUrl() == null - ? this.dataSourceProperties.getUrl() - : this.properties.getUrl(); - - String user = this.properties.getUser() == null - ? this.dataSourceProperties.getUsername() - : this.properties.getUser(); - - String password = this.properties.getPassword() == null - ? this.dataSourceProperties.getPassword() - : this.properties.getPassword(); - + String url = getProperty(this.properties::getUrl, + this.dataSourceProperties::getUrl); + String user = getProperty(this.properties::getUser, + this.dataSourceProperties::getUsername); + String password = getProperty(this.properties::getPassword, + this.dataSourceProperties::getPassword); flyway.setDataSource(url, user, password, this.properties.getInitSqls().toArray(new String[0])); } @@ -159,13 +154,20 @@ public class FlywayAutoConfiguration { return flyway; } + private String getProperty(Supplier property, + Supplier defaultValue) { + String value = property.get(); + return value == null ? defaultValue.get() : value; + } + private void checkLocationExists(String... locations) { if (this.properties.isCheckLocation()) { Assert.state(locations.length != 0, "Migration script locations not configured"); boolean exists = hasAtLeastOneLocation(locations); - Assert.state(exists, () -> "Cannot find migrations location in: " - + Arrays.asList(locations) + Assert.state(exists, + () -> "Cannot find migrations location in: " + Arrays.asList( + locations) + " (please add migrations or check your Flyway configuration)"); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java index 5eef9b8020c..c5a460cc235 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java index 82fa8c741b9..40ad4e4b9ad 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java @@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.liquibase; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import javax.annotation.PostConstruct; import javax.persistence.EntityManagerFactory; @@ -153,22 +154,22 @@ public class LiquibaseAutoConfiguration { } private DataSource createNewDataSource() { - String url = this.properties.getUrl() == null - ? this.dataSourceProperties.getUrl() - : this.properties.getUrl(); - - String user = this.properties.getUser() == null - ? this.dataSourceProperties.getUsername() - : this.properties.getUser(); - - String password = this.properties.getPassword() == null - ? this.dataSourceProperties.getPassword() - : this.properties.getPassword(); - + String url = getProperty(this.properties::getUrl, + this.dataSourceProperties::getUrl); + String user = getProperty(this.properties::getUser, + this.dataSourceProperties::getUsername); + String password = getProperty(this.properties::getPassword, + this.dataSourceProperties::getPassword); return DataSourceBuilder.create().url(url).username(user).password(password) .build(); } + private String getProperty(Supplier property, + Supplier defaultValue) { + String value = property.get(); + return value == null ? defaultValue.get() : value; + } + } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index a205d1fd358..a082b776a32 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 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. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc index 4bbb58325be..a007464bf64 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -2139,7 +2139,10 @@ uses that for migrations. If you like to use a different `DataSource`, you can c one and mark its `@Bean` as `@FlywayDataSource`. If you do so and want two data sources, remember to create another one and mark it as `@Primary`. Alternatively, you can use Flyway's native `DataSource` by setting `spring.flyway.[url,user,password]` -in external properties. +in external properties. Setting either `spring.flyway.url` or `spring.flyway.user` +is sufficent to cause Flyway to use its own `DataSource`. If any of the three +properties has not be set, the value of its equivalent `spring.datasource` property will +be used. There is a {github-code}/spring-boot-samples/spring-boot-sample-flyway[Flyway sample] so that you can see how to set things up. @@ -2175,7 +2178,10 @@ that for migrations. If you need to use a different `DataSource`, you can create mark its `@Bean` as `@LiquibaseDataSource`. If you do so and you want two data sources, remember to create another one and mark it as `@Primary`. Alternatively, you can use Liquibase's native `DataSource` by setting `spring.liquibase.[url,user,password]` in -external properties. +external properties. Setting either `spring.liquibase.url` or `spring.liquibase.user` +is sufficent to cause Liquibase to use its own `DataSource`. If any of the three +properties has not be set, the value of its equivalent `spring.datasource` property will +be used. See {sc-spring-boot-autoconfigure}/liquibase/LiquibaseProperties.{sc-ext}[`LiquibaseProperties`]