mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-09-03 04:26:12 +08:00
Fix NamedParameterJdbcTemplate precedence with database migration tools
See gh-16047
This commit is contained in:
parent
2236e959b6
commit
2c4afb3bd8
@ -46,6 +46,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.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;
|
||||
@ -57,6 +58,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.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.support.JdbcUtils;
|
||||
import org.springframework.jdbc.support.MetaDataAccessException;
|
||||
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
|
||||
@ -76,6 +78,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Jacques-Etienne Beaudet
|
||||
* @author Eddú Meléndez
|
||||
* @author Dominic Gunn
|
||||
* @author Dan Zheng
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@ -321,6 +324,23 @@ public class FlywayAutoConfiguration {
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -359,6 +379,22 @@ public class FlywayAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}";
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,7 @@ import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDepen
|
||||
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;
|
||||
@ -45,6 +46,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.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.util.Assert;
|
||||
@ -58,6 +60,7 @@ import org.springframework.util.Assert;
|
||||
* @author Eddú Meléndez
|
||||
* @author Andy Wilkinson
|
||||
* @author Dominic Gunn
|
||||
* @author Dan Zheng
|
||||
* @since 1.1.0
|
||||
*/
|
||||
@Configuration
|
||||
@ -208,4 +211,20 @@ public class LiquibaseAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
@ -33,6 +47,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -41,6 +56,7 @@ import static org.mockito.Mockito.mock;
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Kazuki Shimizu
|
||||
* @author Dan Zheng
|
||||
*/
|
||||
public class JdbcTemplateAutoConfigurationTests {
|
||||
|
||||
@ -185,6 +201,52 @@ public class JdbcTemplateAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@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)
|
||||
@ -199,6 +261,50 @@ public class JdbcTemplateAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
@ -278,4 +384,66 @@ public class JdbcTemplateAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
databaseChangeLog:
|
||||
- changeSet:
|
||||
id: 1
|
||||
author: dan-zheng
|
||||
changes:
|
||||
- createSequence:
|
||||
sequenceName: hibernate_sequence
|
||||
- createTable:
|
||||
tableName: city
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
type: bigint
|
||||
autoIncrement: true
|
||||
constraints:
|
||||
primaryKey: true
|
||||
nullable: false
|
||||
- column:
|
||||
name: name
|
||||
type: varchar(50)
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: state
|
||||
type: varchar(50)
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: country
|
||||
type: varchar(50)
|
||||
constraints:
|
||||
nullable: false
|
||||
- column:
|
||||
name: map
|
||||
type: varchar(50)
|
||||
constraints:
|
||||
nullable: true
|
||||
- insert:
|
||||
tableName: city
|
||||
columns:
|
||||
- column:
|
||||
name: id
|
||||
value: 1
|
||||
- column:
|
||||
name: name
|
||||
value: Hangzhou
|
||||
- column:
|
||||
name: state
|
||||
value: Zhejiang
|
||||
- column:
|
||||
name: country
|
||||
value: China
|
||||
- column:
|
||||
name: map
|
||||
value: map
|
@ -0,0 +1,9 @@
|
||||
CREATE SEQUENCE HIBERNATE_SEQUENCE;
|
||||
|
||||
CREATE TABLE CITY (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
|
||||
name VARCHAR(30),
|
||||
state VARCHAR(30),
|
||||
country VARCHAR(30),
|
||||
map VARCHAR(30)
|
||||
);
|
@ -0,0 +1 @@
|
||||
INSERT INTO CITY(id, name, state, country, map) VALUES(1, 'Hangzhou', 'Zhejiang', 'China', 'map');
|
Loading…
Reference in New Issue
Block a user