Added liquibase autoconfiguration for database migrations

If Liquibase is on the classpath it will fire up on startup. Various
config options are available (as well as the option to disable it).
Liquibase uses a YAML format for changes (in classpath:db/changelog).
This commit is contained in:
Marcel Overdijk
2014-04-17 23:59:36 +02:00
committed by Dave Syer
parent ed64640ea4
commit 68e33b25c1
22 changed files with 611 additions and 2 deletions

View File

@@ -0,0 +1,83 @@
/*
* 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.liquibase;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import liquibase.integration.spring.SpringLiquibase;
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;
/**
* {@link EnableAutoConfiguration Auto-configuration} for Liquibase.
*
* @author Marcel Overdijk
*/
@Configuration
@ConditionalOnClass(SpringLiquibase.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class LiquibaseAutoConfiguration {
@Configuration
@ConditionalOnMissingBean(SpringLiquibase.class)
@EnableConfigurationProperties(LiquibaseProperties.class)
public static class LiquibaseConfiguration {
@Autowired
private LiquibaseProperties properties = new LiquibaseProperties();
@Autowired
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@Autowired
private DataSource dataSource;
@PostConstruct
public void checkChangelogExists() {
if (this.properties.isCheckChangeLogLocation()) {
Resource resource = this.resourceLoader.getResource(this.properties.getChangeLog());
Assert.state(resource.exists(), "Cannot find changelog location: "
+ resource + " (please add changelog or check your Liquibase configuration)");
}
}
@Bean
public SpringLiquibase liquibase() {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setChangeLog(this.properties.getChangeLog());
liquibase.setContexts(this.properties.getContexts());
liquibase.setDataSource(dataSource);
liquibase.setDefaultSchema(this.properties.getDefaultSchema());
liquibase.setDropFirst(this.properties.isDropFirst());
liquibase.setShouldRun(this.properties.isShouldRun());
return liquibase;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.liquibase;
import javax.validation.constraints.NotNull;
import liquibase.integration.spring.SpringLiquibase;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Configuration properties to configure {@link SpringLiquibase}.
*
* @author Marcel Overdijk
*/
@ConfigurationProperties(prefix = "spring.liquibase", ignoreUnknownFields = false)
public class LiquibaseProperties {
@NotNull
private String changeLog = "classpath:/db/changelog/db.changelog-master.yaml";
private boolean checkChangeLogLocation = true;
private String contexts;
private String defaultSchema;
private boolean dropFirst = false;
private boolean shouldRun = true;
public String getChangeLog() {
return changeLog;
}
public void setChangeLog(String changeLog) {
this.changeLog = changeLog;
}
public boolean isCheckChangeLogLocation() {
return checkChangeLogLocation;
}
public void setCheckChangeLogLocation(boolean checkChangeLogLocation) {
this.checkChangeLogLocation = checkChangeLogLocation;
}
public String getContexts() {
return contexts;
}
public void setContexts(String contexts) {
this.contexts = contexts;
}
public String getDefaultSchema() {
return defaultSchema;
}
public void setDefaultSchema(String defaultSchema) {
this.defaultSchema = defaultSchema;
}
public boolean isDropFirst() {
return dropFirst;
}
public void setDropFirst(boolean dropFirst) {
this.dropFirst = dropFirst;
}
public boolean isShouldRun() {
return shouldRun;
}
public void setShouldRun(boolean shouldRun) {
this.shouldRun = shouldRun;
}
}

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.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoDataAutoConfiguration,\

View File

@@ -0,0 +1,135 @@
/*
* 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.liquibase;
import liquibase.integration.spring.SpringLiquibase;
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.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Tests for {@link LiquibaseAutoConfiguration}.
*
* @author Marcel Overdijk
*/
public class LiquibaseAutoConfigurationTests {
@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(LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.expected.expect(BeanCreationException.class);
this.expected.expectMessage("No qualifying bean");
this.expected.expectMessage("DataSource");
this.context.refresh();
}
@Test
public void testDefaultSpringLiquibase() throws Exception {
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class);
assertEquals("classpath:/db/changelog/db.changelog-master.yaml", liquibase.getChangeLog());
assertNull(liquibase.getContexts());
assertNull(liquibase.getDefaultSchema());
assertFalse(liquibase.isDropFirst());
}
@Test
public void testOverrideChangeLog() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.liquibase.change-log:classpath:/db/changelog/db.changelog-override.xml");
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class);
assertEquals("classpath:/db/changelog/db.changelog-override.xml", liquibase.getChangeLog());
}
@Test
public void testOverrideContexts() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.liquibase.contexts:test, production");
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class);
assertEquals("test, production", liquibase.getContexts());
}
@Test
public void testOverrideDefaultSchema() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.liquibase.default-schema:public");
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class);
assertEquals("public", liquibase.getDefaultSchema());
}
@Test
public void testOverrideDropFirst() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.liquibase.drop-first:true");
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
SpringLiquibase liquibase = this.context.getBean(SpringLiquibase.class);
assertTrue(liquibase.isDropFirst());
}
@Test(expected = BeanCreationException.class)
public void testChangeLogDoesNotExist() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"spring.liquibase.change-log:classpath:/no-such-changelog.yaml");
this.context.register(EmbeddedDataSourceConfiguration.class,
LiquibaseAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class);
this.context.refresh();
}
}

View File

@@ -0,0 +1,20 @@
databaseChangeLog:
- changeSet:
id: 1
author: marceloverdijk
changes:
- createTable:
tableName: customer
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: name
type: varchar(50)
constraints:
nullable: false

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="1" author="marceloverdijk">
<createTable tableName="customer">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(50)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>