Fix dependency order between JdbcTemplate and database migration tools
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
This commit is contained in:
		
							parent
							
								
									331775d6c0
								
							
						
					
					
						commit
						48819253eb
					
				| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +58,11 @@ public abstract class AbstractDependsOnBeanFactoryPostProcessor
 | 
			
		|||
		this.dependsOn = dependsOn;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protected AbstractDependsOnBeanFactoryPostProcessor(Class<?> beanClass,
 | 
			
		||||
			String... dependsOn) {
 | 
			
		||||
		this(beanClass, null, dependsOn);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
 | 
			
		||||
		for (String beanName : getBeanNames(beanFactory)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -74,10 +79,13 @@ public abstract class AbstractDependsOnBeanFactoryPostProcessor
 | 
			
		|||
		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)) {
 | 
			
		||||
		if (this.factoryBeanClass != null) {
 | 
			
		||||
			for (String factoryBeanName : BeanFactoryUtils
 | 
			
		||||
					.beanNamesForTypeIncludingAncestors(beanFactory,
 | 
			
		||||
							this.factoryBeanClass, true, false)) {
 | 
			
		||||
				names.add(BeanFactoryUtils.transformedBeanName(factoryBeanName));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return names;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +53,7 @@ import org.springframework.context.annotation.Configuration;
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -76,7 +79,7 @@ import org.springframework.util.StringUtils;
 | 
			
		|||
@ConditionalOnBean(DataSource.class)
 | 
			
		||||
@ConditionalOnProperty(prefix = "spring.flyway", name = "enabled", matchIfMissing = true)
 | 
			
		||||
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
 | 
			
		||||
		HibernateJpaAutoConfiguration.class })
 | 
			
		||||
		JdbcTemplateAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
 | 
			
		||||
public class FlywayAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
	@Bean
 | 
			
		||||
| 
						 | 
				
			
			@ -202,6 +205,22 @@ public class FlywayAutoConfiguration {
 | 
			
		|||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * 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");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
| 
						 | 
				
			
			@ -220,6 +239,22 @@ public class FlywayAutoConfiguration {
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +39,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +48,7 @@ import org.springframework.context.annotation.Configuration;
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +191,22 @@ public class LiquibaseAutoConfiguration {
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,8 @@ import javax.sql.DataSource;
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -159,6 +161,44 @@ public class JdbcTemplateAutoConfigurationTests {
 | 
			
		|||
				});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@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 {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -216,4 +256,26 @@ public class JdbcTemplateAutoConfigurationTests {
 | 
			
		|||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue