Add autoconfig support for Flyway migrations

Flyway starts up with its default settings if it is on the classpath.
You can also ask Boot to barf if the migration scripts are missing.

Fixes gh-730
This commit is contained in:
Dave Syer 2014-05-01 19:37:25 +01:00
parent 68e33b25c1
commit 5548b24c4c
20 changed files with 498 additions and 3 deletions

View File

@ -37,6 +37,11 @@
<artifactId>jackson-datatype-joda</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.googlecode.flyway</groupId>
<artifactId>flyway-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>

View File

@ -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;
}
}
}

View File

@ -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<String> locations = Arrays.asList("db/migrations");
private List<String> schemas = new ArrayList<String>();
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<String> locations) {
this.locations = locations;
}
public List<String> getLocations() {
return this.locations;
}
public List<String> getSchemas() {
return this.schemas;
}
public void setSchemas(List<String> schemas) {
this.schemas = schemas;
}
public void setCheckLocation(boolean checkLocation) {
this.checkLocation = checkLocation;
}
public boolean isCheckLocation() {
return this.checkLocation;
}
}

View File

@ -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,\

View File

@ -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();
}
}

View File

@ -51,6 +51,7 @@
<commons-dbcp.version>1.4</commons-dbcp.version>
<commons-pool.version>1.6</commons-pool.version>
<crashub.version>1.3.0-beta14</crashub.version>
<flyway.version>2.2.1</flyway.version>
<freemarker.version>2.3.20</freemarker.version>
<gemfire.version>7.0.1</gemfire.version>
<gradle.version>1.6</gradle.version>
@ -80,7 +81,7 @@
<mongodb.version>2.11.4</mongodb.version>
<mysql.version>5.1.30</mysql.version>
<neo4j.version>2.0.2</neo4j.version>
<reactor.version>1.1.0.RELEASE</reactor.version>
<reactor.version>1.1.1.BUILD-SNAPSHOT</reactor.version>
<servlet-api.version>3.0.1</servlet-api.version>
<slf4j.version>1.7.7</slf4j.version>
<snakeyaml.version>1.13</snakeyaml.version>
@ -146,6 +147,11 @@
<artifactId>jackson-datatype-joda</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.flyway</groupId>
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>

View File

@ -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<VERSION>__<NAME>.sql` (with
`<VERSION>` 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]]

View File

@ -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.

View File

@ -32,6 +32,7 @@
<module>spring-boot-sample-data-mongodb</module>
<module>spring-boot-sample-data-redis</module>
<module>spring-boot-sample-data-rest</module>
<module>spring-boot-sample-flyway</module>
<module>spring-boot-sample-integration</module>
<module>spring-boot-sample-jetty</module>
<module>spring-boot-sample-liquibase</module>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-flyway</artifactId>
<name>Spring Boot Flyway Sample</name>
<description>Spring Boot Flyway Sample</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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');

View File

@ -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));
}
}

View File

@ -5,7 +5,7 @@
<!-- Your own application should inherit from spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-samples</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-sample-liquibase</artifactId>
<name>Spring Boot Liquibase Sample</name>

View File

@ -29,6 +29,7 @@
<module>spring-boot-starter-data-mongodb</module>
<module>spring-boot-starter-data-neo4j</module>
<module>spring-boot-starter-data-rest</module>
<module>spring-boot-starter-flyway</module>
<module>spring-boot-starter-freemarker</module>
<module>spring-boot-starter-integration</module>
<module>spring-boot-starter-jdbc</module>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-flyway</artifactId>
<name>Spring Boot Flyway Starter</name>
<description>Spring Boot Flyway Starter</description>
<url>http://projects.spring.io/spring-boot/</url>
<organization>
<name>Pivotal Software, Inc.</name>
<url>http://www.spring.io</url>
</organization>
<properties>
<main.basedir>${basedir}/../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.flyway</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1 @@
provides: liquibase-core

View File

@ -4,7 +4,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>1.0.2.BUILD-SNAPSHOT</version>
<version>1.1.0.BUILD-SNAPSHOT</version>
</parent>
<artifactId>spring-boot-starter-liquibase</artifactId>
<name>Spring Boot Liquibase Starter</name>

View File

@ -101,6 +101,11 @@
<artifactId>spring-boot-starter-data-rest</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>