diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java index db045bf800..c5397983be 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2017 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. @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.flyway; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.annotation.PostConstruct; @@ -94,19 +95,20 @@ public class FlywayAutoConfiguration { private final FlywayMigrationStrategy migrationStrategy; - private FlywayCallback[] flywayCallbacks; + private List flywayCallbacks; public FlywayConfiguration(FlywayProperties properties, ResourceLoader resourceLoader, ObjectProvider dataSource, @FlywayDataSource ObjectProvider flywayDataSource, ObjectProvider migrationStrategy, - ObjectProvider flywayCallbacks) { + ObjectProvider> flywayCallbacks) { this.properties = properties; this.resourceLoader = resourceLoader; this.dataSource = dataSource.getIfUnique(); this.flywayDataSource = flywayDataSource.getIfAvailable(); this.migrationStrategy = migrationStrategy.getIfAvailable(); - this.flywayCallbacks = flywayCallbacks.getIfAvailable(); + this.flywayCallbacks = flywayCallbacks + .getIfAvailable(() -> Collections.emptyList()); } @PostConstruct @@ -146,9 +148,8 @@ public class FlywayAutoConfiguration { else { flyway.setDataSource(this.dataSource); } - if (this.flywayCallbacks != null && this.flywayCallbacks.length > 0) { - flyway.setCallbacks(this.flywayCallbacks); - } + flyway.setCallbacks(this.flywayCallbacks + .toArray(new FlywayCallback[this.flywayCallbacks.size()])); flyway.setLocations(this.properties.getLocations().toArray(new String[0])); return flyway; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java index 70d3abbeaa..4a0658c57f 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.autoconfigure.flyway; +import java.sql.Connection; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -25,13 +26,14 @@ import javax.sql.DataSource; import org.flywaydb.core.Flyway; import org.flywaydb.core.api.MigrationVersion; -import org.flywaydb.core.api.callback.BaseFlywayCallback; +import org.flywaydb.core.api.callback.FlywayCallback; import org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.mockito.InOrder; import org.springframework.beans.factory.BeanCreationException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; @@ -44,12 +46,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.core.env.MapPropertySource; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.stereotype.Component; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; /** * Tests for {@link FlywayAutoConfiguration}. @@ -204,16 +210,6 @@ public class FlywayAutoConfigurationTests { assertThat(initializer.getOrder()).isEqualTo(Ordered.HIGHEST_PRECEDENCE); } - @Test - public void customFlywayCallback() throws Exception { - registerAndRefresh(EmbeddedDataSourceConfiguration.class, - FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, - MockOneFlywayCallback.class, MockTwoFlywayCallback.class); - assertThat(this.context.getBean(Flyway.class)).isNotNull(); - this.context.getBean(MockOneFlywayCallback.class).assertCalled(); - this.context.getBean(MockTwoFlywayCallback.class).assertCalled(); - } - @Test public void customFlywayWithJpa() throws Exception { registerAndRefresh(CustomFlywayWithJpaConfiguration.class, @@ -258,6 +254,23 @@ public class FlywayAutoConfigurationTests { "classpath:db/vendors/h2", "classpath:db/changelog"); } + @Test + public void callbacksAreConfiguredAndOrdered() throws Exception { + registerAndRefresh(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, + CallbackConfiguration.class); + assertThat(this.context.getBeansOfType(Flyway.class)).hasSize(1); + Flyway flyway = this.context.getBean(Flyway.class); + FlywayCallback callbackOne = this.context.getBean("callbackOne", + FlywayCallback.class); + FlywayCallback callbackTwo = this.context.getBean("callbackTwo", + FlywayCallback.class); + assertThat(flyway.getCallbacks()).containsExactly(callbackTwo, callbackOne); + InOrder orderedCallbacks = inOrder(callbackOne, callbackTwo); + orderedCallbacks.verify(callbackTwo).beforeMigrate(any(Connection.class)); + orderedCallbacks.verify(callbackOne).beforeMigrate(any(Connection.class)); + } + private void registerAndRefresh(Class... annotatedClasses) { this.context.register(annotatedClasses); this.context.refresh(); @@ -337,34 +350,21 @@ public class FlywayAutoConfigurationTests { } - @Component - protected static class MockOneFlywayCallback - extends BaseFlywayCallback { - - private boolean called = false; - - public MockOneFlywayCallback() { - this.called = true; - } + @Configuration + static class CallbackConfiguration { - public void assertCalled() { - assertThat(this.called).isTrue(); + @Bean + @Order(1) + public FlywayCallback callbackOne() { + return mock(FlywayCallback.class); } - } - - @Component - protected static class MockTwoFlywayCallback - extends BaseFlywayCallback { - - private boolean called = false; - public MockTwoFlywayCallback() { - this.called = true; + @Bean + @Order(0) + public FlywayCallback callbackTwo() { + return mock(FlywayCallback.class); } - public void assertCalled() { - assertThat(this.called).isTrue(); - } } } diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 95c65a4eeb..c3a16e508f 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1952,8 +1952,12 @@ Boot will call `Flyway.migrate()` to perform the database migration. If you woul more control, provide a `@Bean` that implements {sc-spring-boot-autoconfigure}/flyway/FlywayMigrationStrategy.{sc-ext}[`FlywayMigrationStrategy`]. -TIP: If you want to make use of http://flywaydb.org/documentation/callbacks.html[Flyway -callbacks], those scripts should also live in the `classpath:db/migration` folder. +Flyway supports SQL and Java http://flywaydb.org/documentation/callbacks.html[callbacks]. +To use SQL-based callbacks, place the callback scripts in the `classpath:db/migration` +folder. To use Java-based callbacks, create one or more beans that implement +`FlywayCallback` or, preferably, extend `BaseFlywayCallback`. Any such beans will be +automatically registered with `Flyway`. They can be ordered using `@Order` or by +implementing `Ordered`. By default Flyway will autowire the (`@Primary`) `DataSource` in your context and use that for migrations. If you like to use a different `DataSource` you can create