Skip to content

Database migrations may not have run before NamedParameterJdbcTemplate is used #16047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
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.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -57,6 +58,7 @@
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.jdbc.support.MetaDataAccessException;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
Expand All @@ -76,6 +78,7 @@
* @author Jacques-Etienne Beaudet
* @author Eddú Meléndez
* @author Dominic Gunn
* @author Dan Zheng
* @since 1.1.0
*/
@SuppressWarnings("deprecation")
Expand Down Expand Up @@ -301,6 +304,23 @@ protected static class FlywayInitializerJdbcOperationsDependencyConfiguration

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

}

}

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

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

}
Expand Down Expand Up @@ -339,6 +359,22 @@ public FlywayJdbcOperationsDependencyConfiguration() {

}

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

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

}

private static class LocationResolver {

private static final String VENDOR_PLACEHOLDER = "{vendor}";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2012-2019 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.namedparam.NamedParameterJdbcOperations;

/**
* {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all
* {@link NamedParameterJdbcOperations} beans should "depend on" one or more specific
* beans.
*
* @author Dan Zheng
* @since 2.1.x
* @see BeanDefinition#setDependsOn(String[])
*/
public class NamedParameterJdbcOperationsDependsOnPostProcessor
extends AbstractDependsOnBeanFactoryPostProcessor {

public NamedParameterJdbcOperationsDependsOnPostProcessor(String... dependsOn) {
super(NamedParameterJdbcOperations.class, dependsOn);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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.NamedParameterJdbcOperationsDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
Expand All @@ -45,6 +46,7 @@
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;
Expand All @@ -58,6 +60,7 @@
* @author Eddú Meléndez
* @author Andy Wilkinson
* @author Dominic Gunn
* @author Dan Zheng
* @since 1.1.0
*/
@Configuration
Expand Down Expand Up @@ -205,4 +208,20 @@ public LiquibaseJdbcOperationsDependencyConfiguration() {

}

/**
* Additional configuration to ensure that {@link NamedParameterJdbcOperations} beans
* depend on the liquibase bean.
*/
@Configuration
@ConditionalOnClass(NamedParameterJdbcOperations.class)
@ConditionalOnBean(NamedParameterJdbcOperations.class)
protected static class LiquibaseNamedParameterJdbcOperationsDependencyConfiguration
extends NamedParameterJdbcOperationsDependsOnPostProcessor {

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

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,27 @@

package org.springframework.boot.autoconfigure.jdbc;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.junit.Test;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -33,6 +47,7 @@
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;

/**
Expand All @@ -41,6 +56,7 @@
* @author Dave Syer
* @author Stephane Nicoll
* @author Kazuki Shimizu
* @author Dan Zheng
*/
public class JdbcTemplateAutoConfigurationTests {

Expand Down Expand Up @@ -185,6 +201,52 @@ public void testDependencyToFlyway() {
});
}

@Test
public void testDependencyToFlywayWithJdbcTemplateMixed() {
this.contextRunner
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
.withPropertyValues("spring.flyway.locations:classpath:db/city_np")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.getBean(JdbcTemplate.class)).isNotNull();
assertThat(context.getBean(
NamedParameterDataSourceMigrationValidator.class).count)
.isEqualTo(1);
});
}

@Test
public void testDependencyToFlywayWithOnlyNamedParameterJdbcTemplate() {
ApplicationContextRunner contextRunner1 = new ApplicationContextRunner()
.withPropertyValues("spring.datasource.initialization-mode=never",
"spring.datasource.generate-unique-name=true")
.withConfiguration(
AutoConfigurations.of(DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class,
OnlyNamedParameterJdbcTemplateAutoConfiguration.class));
contextRunner1
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
.withPropertyValues("spring.flyway.locations:classpath:db/city_np")
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.containsBean("jdbcTemplate")).isFalse();
try {
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
fail("org.springframework.boot.autoconfigure.jdbc.JdcTemplate should not exist in the application context");
}
catch (NoSuchBeanDefinitionException ex) {

}
assertThat(context.getBean(NamedParameterJdbcTemplate.class))
.isNotNull();
assertThat(context.getBean(
NamedParameterDataSourceMigrationValidator.class).count)
.isEqualTo(1);
});
}

@Test
public void testDependencyToLiquibase() {
this.contextRunner.withUserConfiguration(DataSourceMigrationValidator.class)
Expand All @@ -199,6 +261,50 @@ public void testDependencyToLiquibase() {
});
}

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

@Test
public void testDependencyToLiquibaseWithOnlyNamedParameterJdbcTemplate() {
this.contextRunner
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
.withPropertyValues(
"spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city-np.yaml")
.withConfiguration(AutoConfigurations.of(
OnlyNamedParameterJdbcTemplateAutoConfiguration.class,
LiquibaseAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasNotFailed();
assertThat(context.containsBean("jdbcTemplate")).isFalse();
try {
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
fail("org.springframework.boot.autoconfigure.jdbc.JdcTemplate should not exist in the application context");
}
catch (NoSuchBeanDefinitionException ex) {

}
assertThat(context.getBean(NamedParameterJdbcTemplate.class))
.isNotNull();
assertThat(context.getBean(
NamedParameterDataSourceMigrationValidator.class).count)
.isEqualTo(1);
});
}

@Configuration
static class CustomConfiguration {

Expand Down Expand Up @@ -278,4 +384,66 @@ static class DataSourceMigrationValidator {

}

static class NamedParameterDataSourceMigrationValidator {

private final Integer count;

NamedParameterDataSourceMigrationValidator(
NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
String sql = "SELECT COUNT(*) from CITY WHERE id = :id";
Map<String, Long> param = new HashMap<>();
param.put("id", 1L);
this.count = namedParameterJdbcTemplate.queryForObject(sql, param,
Integer.class);
}

}

@Configuration
@ConditionalOnClass({ DataSource.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
JdbcTemplateAutoConfiguration.class })
@AutoConfigureBefore({ FlywayAutoConfiguration.class,
LiquibaseAutoConfiguration.class })
@EnableConfigurationProperties(JdbcProperties.class)
static class OnlyNamedParameterJdbcTemplateAutoConfiguration
implements BeanDefinitionRegistryPostProcessor {

@Bean
public NamedParameterJdbcTemplate myNamedParameterJdbcTemplate(
DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
// do nothing
}

/**
* <p>
* we should remove the jdbc template bean definition to keep only
* NamedParameterJdbcTemplate is registerd in the bean container
* </p>
* @param registry the bean definition registry.
* @throws BeansException if the bean registry have any exception.
*/
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
String[] excludeBeanNames = new String[] { "jdbcTemplate",
"namedParameterJdbcTemplate" };
for (String beanName : excludeBeanNames) {
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
if (beanDefinition != null) {
registry.removeBeanDefinition(beanName);
}
}

}

}

}
Loading