diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index d2b1de82797b..891184be9de4 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -37,6 +37,11 @@ jackson-datatype-joda true + + com.googlecode.flyway + flyway-core + true + commons-dbcp commons-dbcp 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 new file mode 100644 index 000000000000..18d0e91925cf --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.flyway; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; + +import com.googlecode.flyway.core.Flyway; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for Flyway database migrations. + * + * @author Dave Syer + */ +@Configuration +@ConditionalOnClass(Flyway.class) +@AutoConfigureAfter(DataSourceAutoConfiguration.class) +public class FlywayAutoConfiguration { + + @Configuration + @ConditionalOnMissingBean(Flyway.class) + @EnableConfigurationProperties(FlywayProperties.class) + public static class LiquibaseConfiguration { + + @Autowired + private FlywayProperties properties = new FlywayProperties(); + + @Autowired + private ResourceLoader resourceLoader = new DefaultResourceLoader(); + + @Autowired + private DataSource dataSource; + + @PostConstruct + public void checkLocationExists() { + if (this.properties.isCheckLocation()) { + + Assert.state(!this.properties.getLocations().isEmpty(), + "Migration script locations not configured"); + boolean exists = false; + for (String location : this.properties.getLocations()) { + Resource resource = this.resourceLoader.getResource(location); + exists = !exists && resource.exists(); + } + Assert.state(exists, "Cannot find migrations location in: " + + this.properties.getLocations() + + " (please add migrations or check your Flyway configuration)"); + } + } + + @Bean + public Flyway flyway(DataSource dataSource) { + Flyway flyway = new Flyway(); + flyway.setLocations(this.properties.getLocations().toArray(new String[0])); + flyway.setSchemas(this.properties.getSchemas().toArray(new String[0])); + flyway.setInitVersion(this.properties.getInitVersion()); + flyway.setSqlMigrationPrefix(this.properties.getPrefix()); + flyway.setSqlMigrationSuffix(this.properties.getSuffix()); + flyway.setDataSource(dataSource); + flyway.migrate(); + return flyway; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java new file mode 100644 index 000000000000..25114c2fbe84 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayProperties.java @@ -0,0 +1,92 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.flyway; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Configuration properties to configure Flyway. + * + * @author Dave Syer + */ +@ConfigurationProperties(prefix = "spring.flyway", ignoreUnknownFields = false) +public class FlywayProperties { + + private List locations = Arrays.asList("db/migrations"); + + private List schemas = new ArrayList(); + + private String prefix = "V"; + + private String suffix = ".sql"; + + private String initVersion = "1"; + + private boolean checkLocation; + + public String getPrefix() { + return this.prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getSuffix() { + return this.suffix; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public String getInitVersion() { + return this.initVersion; + } + + public void setInitVersion(String initVersion) { + this.initVersion = initVersion; + } + + public void setLocations(List locations) { + this.locations = locations; + } + + public List getLocations() { + return this.locations; + } + + public List getSchemas() { + return this.schemas; + } + + public void setSchemas(List schemas) { + this.schemas = schemas; + } + + public void setCheckLocation(boolean checkLocation) { + this.checkLocation = checkLocation; + } + + public boolean isCheckLocation() { + return this.checkLocation; + } +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index d6ae3f7b9892..5db10f70e530 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -19,6 +19,7 @@ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\ org.springframework.boot.autoconfigure.jms.JmsTemplateAutoConfiguration,\ org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\ +org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\ org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\ org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\ org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\ 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 new file mode 100644 index 000000000000..198f5cd8aa62 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfigurationTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.flyway; + +import java.util.Arrays; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import com.googlecode.flyway.core.Flyway; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link LiquibaseAutoConfiguration}. + * + * @author Dave Syer + */ +public class FlywayAutoConfigurationTests { + + @Rule + public ExpectedException expected = ExpectedException.none(); + + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testNoDataSource() throws Exception { + this.context.register(FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.expected.expect(BeanCreationException.class); + this.expected.expectMessage("No qualifying bean"); + this.expected.expectMessage("DataSource"); + this.context.refresh(); + } + + @Test + public void testDefaultFlyway() throws Exception { + this.context + .register(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + Flyway flyway = this.context.getBean(Flyway.class); + assertEquals("[classpath:db/migrations]", Arrays.asList(flyway.getLocations()) + .toString()); + } + + @Test + public void testOverrideLocations() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.flyway.locations:classpath:db/changelog"); + this.context + .register(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + Flyway flyway = this.context.getBean(Flyway.class); + assertEquals("[classpath:db/changelog]", Arrays.asList(flyway.getLocations()) + .toString()); + } + + @Test + public void testOverrideSchemas() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, "spring.flyway.schemas:public"); + this.context + .register(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + Flyway flyway = this.context.getBean(Flyway.class); + assertEquals("[public]", Arrays.asList(flyway.getSchemas()).toString()); + } + + @Test(expected = BeanCreationException.class) + public void testChangeLogDoesNotExist() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.flyway.locations:no-such-dir"); + this.context + .register(EmbeddedDataSourceConfiguration.class, + FlywayAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + } +} diff --git a/spring-boot-autoconfigure/src/test/resources/db/migrations/V1__init.sql b/spring-boot-autoconfigure/src/test/resources/db/migrations/V1__init.sql new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 4f36d60c7be6..3f7fed899db2 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -51,6 +51,7 @@ 1.4 1.6 1.3.0-beta14 + 2.2.1 2.3.20 7.0.1 1.6 @@ -80,7 +81,7 @@ 2.11.4 5.1.30 2.0.2 - 1.1.0.RELEASE + 1.1.1.BUILD-SNAPSHOT 3.0.1 1.7.7 1.13 @@ -146,6 +147,11 @@ jackson-datatype-joda ${jackson.version} + + com.googlecode.flyway + flyway-core + ${flyway.version} + com.zaxxer HikariCP diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index e7ca6d215c95..d8a405a5337d 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -1095,6 +1095,20 @@ Spring Boot works fine with higher level migration tools http://flywaydb.org/[Fl Flyway because it is easier on the eyes, and it isn't very common to need platform independence: usually only one or at most couple of platforms is needed. +[[howto-execute-flyway-database-migrations-on-startup]] +==== Execute Flyway database migrations on startup +To automatically run Flyway database migrations on startup, add the +`spring-boot-starter-flyway` to your classpath. + +The migrations are scripts in the form `V__.sql` (with +`` an underscore-separated version, e.g. "1" or "2_1"). By +default they live in a folder `classpath:db/migrations` but you can +modify that using `flyway.locations` (a list). See +{sc-spring-boot-autoconfigure}/flyway/FlywayProperties.{sc-ext}[`FlywayProperties`] +for details of available settings like schemas etc. + +There is a {github-code}/spring-boot-samples/spring-boot-sample-flyway[Flyway sample] so +you can see how to set things up. [[howto-execute-liquibase-database-migrations-on-startup]] diff --git a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc index 88096747841a..7cd60451784b 100644 --- a/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc +++ b/spring-boot-docs/src/main/asciidoc/using-spring-boot.adoc @@ -238,6 +238,9 @@ and Hibernate. |`spring-boot-starter-jdbc` |JDBC Database support. +|`spring-boot-starter-flyway` +|Support for Flyway database migrations. + |`spring-boot-starter-liquibase` |Support for Liquibase database migrations. diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index 9def9bce5e68..1db66ee63316 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -32,6 +32,7 @@ spring-boot-sample-data-mongodb spring-boot-sample-data-redis spring-boot-sample-data-rest + spring-boot-sample-flyway spring-boot-sample-integration spring-boot-sample-jetty spring-boot-sample-liquibase diff --git a/spring-boot-samples/spring-boot-sample-flyway/pom.xml b/spring-boot-samples/spring-boot-sample-flyway/pom.xml new file mode 100644 index 000000000000..8d7fa803ec3f --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-flyway/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-samples + 1.1.0.BUILD-SNAPSHOT + + spring-boot-sample-flyway + Spring Boot Flyway Sample + Spring Boot Flyway Sample + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-flyway + + + org.hsqldb + hsqldb + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java b/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java new file mode 100644 index 000000000000..52437aadb512 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-flyway/src/main/java/sample/flyway/SampleFlywayApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012-2013 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.flyway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableAutoConfiguration +public class SampleFlywayApplication { + + public static void main(String[] args) throws Exception { + SpringApplication.run(SampleFlywayApplication.class, args); + } +} diff --git a/spring-boot-samples/spring-boot-sample-flyway/src/main/resources/db/migrations/V1__init.sql b/spring-boot-samples/spring-boot-sample-flyway/src/main/resources/db/migrations/V1__init.sql new file mode 100644 index 000000000000..8038f342ea2c --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-flyway/src/main/resources/db/migrations/V1__init.sql @@ -0,0 +1,7 @@ +CREATE TABLE PERSON ( + id INTEGER GENERATED BY DEFAULT AS IDENTITY, + first_name varchar(255) not null, + last_name varchar(255) not null +); + +insert into PERSON (first_name, last_name) values ('Dave', 'Syer'); \ No newline at end of file diff --git a/spring-boot-samples/spring-boot-sample-flyway/src/test/java/sample/flyway/SampleLiquibaseApplicationTests.java b/spring-boot-samples/spring-boot-sample-flyway/src/test/java/sample/flyway/SampleLiquibaseApplicationTests.java new file mode 100644 index 000000000000..a9bb8c96c561 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-flyway/src/test/java/sample/flyway/SampleLiquibaseApplicationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sample.flyway; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes=SampleFlywayApplication.class) +public class SampleLiquibaseApplicationTests { + + @Autowired + private JdbcTemplate template; + + @Test + public void testDefaultSettings() throws Exception { + assertEquals(new Integer(1), template.queryForObject("SELECT COUNT(*) from PERSON", Integer.class)); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-liquibase/pom.xml b/spring-boot-samples/spring-boot-sample-liquibase/pom.xml index f0522496bee0..9bfe000ae060 100644 --- a/spring-boot-samples/spring-boot-sample-liquibase/pom.xml +++ b/spring-boot-samples/spring-boot-sample-liquibase/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-samples - 1.0.2.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT spring-boot-sample-liquibase Spring Boot Liquibase Sample diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index f8067c5defc6..a746bc6f7216 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -29,6 +29,7 @@ spring-boot-starter-data-mongodb spring-boot-starter-data-neo4j spring-boot-starter-data-rest + spring-boot-starter-flyway spring-boot-starter-freemarker spring-boot-starter-integration spring-boot-starter-jdbc diff --git a/spring-boot-starters/spring-boot-starter-flyway/pom.xml b/spring-boot-starters/spring-boot-starter-flyway/pom.xml new file mode 100644 index 000000000000..3594fdfe5e48 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-flyway/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starters + 1.1.0.BUILD-SNAPSHOT + + spring-boot-starter-flyway + Spring Boot Flyway Starter + Spring Boot Flyway Starter + http://projects.spring.io/spring-boot/ + + Pivotal Software, Inc. + http://www.spring.io + + + ${basedir}/../.. + + + + ${project.groupId} + spring-boot-starter-jdbc + ${project.version} + + + com.googlecode.flyway + flyway-core + + + diff --git a/spring-boot-starters/spring-boot-starter-flyway/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-flyway/src/main/resources/META-INF/spring.provides new file mode 100644 index 000000000000..cec4231369a4 --- /dev/null +++ b/spring-boot-starters/spring-boot-starter-flyway/src/main/resources/META-INF/spring.provides @@ -0,0 +1 @@ +provides: liquibase-core \ No newline at end of file diff --git a/spring-boot-starters/spring-boot-starter-liquibase/pom.xml b/spring-boot-starters/spring-boot-starter-liquibase/pom.xml index c7d482217515..d3eee22e2af2 100644 --- a/spring-boot-starters/spring-boot-starter-liquibase/pom.xml +++ b/spring-boot-starters/spring-boot-starter-liquibase/pom.xml @@ -4,7 +4,7 @@ org.springframework.boot spring-boot-starters - 1.0.2.BUILD-SNAPSHOT + 1.1.0.BUILD-SNAPSHOT spring-boot-starter-liquibase Spring Boot Liquibase Starter diff --git a/spring-boot-starters/spring-boot-starter-parent/pom.xml b/spring-boot-starters/spring-boot-starter-parent/pom.xml index 7ffc194cec19..413456ed6257 100644 --- a/spring-boot-starters/spring-boot-starter-parent/pom.xml +++ b/spring-boot-starters/spring-boot-starter-parent/pom.xml @@ -101,6 +101,11 @@ spring-boot-starter-data-rest ${spring-boot.version} + + org.springframework.boot + spring-boot-starter-flyway + ${spring-boot.version} + org.springframework.boot spring-boot-starter-freemarker