Skip to content

Commit

Permalink
Fix dependency order between JdbcTemplate and database migration tools
Browse files Browse the repository at this point in the history
This commit makes sure that Flyway/Liquibase migrates the schema if
necessary before a `JdbcTemplate` is made available as an injection
point.

This commit also adds a test that validates simple datasource
initialization (spring.datasource.*) happens before a `JdbcTemplate`
bean can be used.

Closes gh-13155
  • Loading branch information
snicoll committed Jul 11, 2018
1 parent 331775d commit 4881925
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -58,6 +58,11 @@ protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
this.dependsOn = dependsOn;
}

protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
String... dependsOn) {
this(beanClass, null, dependsOn);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
for (String beanName : getBeanNames(beanFactory)) {
Expand All @@ -74,9 +79,12 @@ private Iterable<String> getBeanNames(ListableBeanFactory beanFactory) {
Set<String> names = new HashSet<>();
names.addAll(Arrays.asList(BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory, this.beanClass, true, false)));
for (String factoryBeanName : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory, this.factoryBeanClass, true, false)) {
names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName));
if (this.factoryBeanClass != null) {
for (String factoryBeanName : BeanFactoryUtils
.beanNamesForTypeIncludingAncestors(beanFactory,
this.factoryBeanClass, true, false)) {
names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName));
}
}
return names;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
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.jdbc.JdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
Expand All @@ -51,6 +53,7 @@
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
Expand All @@ -76,7 +79,7 @@
@ConditionalOnBean(DataSource.class)
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class })
JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
public class FlywayAutoConfiguration {

@Bean
Expand Down Expand Up @@ -202,6 +205,22 @@ public FlywayInitializerJpaDependencyConfiguration() {

}

/**
* Additional configuration to ensure that {@link JdbcOperations} beans depend-on
* the {@code flywayInitializer} bean.
*/
@Configuration
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
protected static class FlywayInitializerJdbcOperationsDependencyConfiguration
extends JdbcOperationsDependsOnPostProcessor {

public FlywayInitializerJdbcOperationsDependencyConfiguration() {
super("flywayInitializer");
}

}

}

/**
Expand All @@ -220,6 +239,22 @@ public FlywayJpaDependencyConfiguration() {

}

/**
* Additional configuration to ensure that {@link JdbcOperations} beans depend-on the
* {@code flyway} bean.
*/
@Configuration
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
protected static class FlywayJdbcDependencyConfiguration
extends JdbcOperationsDependsOnPostProcessor {

public FlywayJdbcDependencyConfiguration() {
super("flyway");
}

}

private static class SpringBootFlyway extends Flyway {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.
* 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.jdbc;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
import org.springframework.jdbc.core.JdbcOperations;

/**
* {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all
* {@link JdbcOperations} beans should "depend on" one or more specific beans.
*
* @author Marcel Overdijk
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.1.0
* @see BeanDefinition#setDependsOn(String[])
*/
public class JdbcOperationsDependsOnPostProcessor
extends AbstractDependsOnBeanFactoryPostProcessor {

public JdbcOperationsDependsOnPostProcessor(String... dependsOn) {
super(JdbcOperations.class, dependsOn);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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.jdbc.JdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
Expand All @@ -47,6 +48,7 @@
import org.springframework.context.annotation.Import;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;
Expand Down Expand Up @@ -189,6 +191,22 @@ public LiquibaseJpaDependencyConfiguration() {

}

/**
* Additional configuration to ensure that {@link JdbcOperations} beans depend-on the
* liquibase bean.
*/
@Configuration
@ConditionalOnClass(JdbcOperations.class)
@ConditionalOnBean(JdbcOperations.class)
protected static class LiquibaseJdbcOperationsDependencyConfiguration
extends JdbcOperationsDependsOnPostProcessor {

public LiquibaseJdbcOperationsDependencyConfiguration() {
super("liquibase");
}

}

/**
* A custom {@link SpringLiquibase} extension that closes the underlying
* {@link DataSource} once the database has been migrated.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import org.junit.Test;

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down Expand Up @@ -159,6 +161,44 @@ public void testExistingCustomNamedParameterJdbcTemplate() {
});
}

@Test
public void testDependencyToDataSourceInitialization() {
this.contextRunner.withUserConfiguration(DataSourceInitializationValidator.class)
.withPropertyValues("spring.datasource.initialization-mode=always")
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context
.getBean(DataSourceInitializationValidator.class).count)
.isEqualTo(1);
});
}

@Test
public void testDependencyToFlyway() {
this.contextRunner.withUserConfiguration(DataSourceMigrationValidator.class)
.withPropertyValues("spring.flyway.locations:classpath:db/city")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.getBean(DataSourceMigrationValidator.class).count)
.isEqualTo(0);
});
}

@Test
public void testDependencyToLiquibase() {
this.contextRunner.withUserConfiguration(DataSourceMigrationValidator.class)
.withPropertyValues(
"spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city.yaml")
.withConfiguration(
AutoConfigurations.of(LiquibaseAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.getBean(DataSourceMigrationValidator.class).count)
.isEqualTo(0);
});
}

@Configuration
static class CustomConfiguration {

Expand Down Expand Up @@ -216,4 +256,26 @@ public JdbcTemplate test2Template() {

}

static class DataSourceInitializationValidator {

private final Integer count;

DataSourceInitializationValidator(JdbcTemplate jdbcTemplate) {
this.count = jdbcTemplate.queryForObject("SELECT COUNT(*) from BAR",
Integer.class);
}

}

static class DataSourceMigrationValidator {

private final Integer count;

DataSourceMigrationValidator(JdbcTemplate jdbcTemplate) {
this.count = jdbcTemplate.queryForObject("SELECT COUNT(*) from CITY",
Integer.class);
}

}

}

0 comments on commit 4881925

Please sign in to comment.