Skip to content

Commit 2c4afb3

Browse files
clevertensionsnicoll
authored andcommitted
Fix NamedParameterJdbcTemplate precedence with database migration tools
See gh-16047
1 parent 2236e95 commit 2c4afb3

File tree

7 files changed

+328
-0
lines changed

7 files changed

+328
-0
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
4747
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
4848
import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration;
49+
import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
4950
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
5051
import org.springframework.boot.context.properties.ConfigurationPropertiesBinding;
5152
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -57,6 +58,7 @@
5758
import org.springframework.core.convert.converter.GenericConverter;
5859
import org.springframework.core.io.ResourceLoader;
5960
import org.springframework.jdbc.core.JdbcOperations;
61+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
6062
import org.springframework.jdbc.support.JdbcUtils;
6163
import org.springframework.jdbc.support.MetaDataAccessException;
6264
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
@@ -76,6 +78,7 @@
7678
* @author Jacques-Etienne Beaudet
7779
* @author Eddú Meléndez
7880
* @author Dominic Gunn
81+
* @author Dan Zheng
7982
* @since 1.1.0
8083
*/
8184
@SuppressWarnings("deprecation")
@@ -321,6 +324,23 @@ protected static class FlywayInitializerJdbcOperationsDependencyConfiguration
321324

322325
public FlywayInitializerJdbcOperationsDependencyConfiguration() {
323326
super("flywayInitializer");
327+
328+
}
329+
330+
}
331+
332+
/**
333+
* Additional configuration to ensure that {@link NamedParameterJdbcOperations}
334+
* beans depend on the {@code flywayInitializer} bean.
335+
*/
336+
@Configuration
337+
@ConditionalOnClass(NamedParameterJdbcOperations.class)
338+
@ConditionalOnBean(NamedParameterJdbcOperations.class)
339+
protected static class FlywayInitializerNamedParameterJdbcOperationsDependencyConfiguration
340+
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
341+
342+
public FlywayInitializerNamedParameterJdbcOperationsDependencyConfiguration() {
343+
super("flywayInitializer");
324344
}
325345

326346
}
@@ -359,6 +379,22 @@ public FlywayJdbcOperationsDependencyConfiguration() {
359379

360380
}
361381

382+
/**
383+
* Additional configuration to ensure that {@link NamedParameterJdbcOperations} beans
384+
* depend on the {@code flyway} bean.
385+
*/
386+
@Configuration
387+
@ConditionalOnClass(NamedParameterJdbcOperations.class)
388+
@ConditionalOnBean(NamedParameterJdbcOperations.class)
389+
protected static class FlywayNamedParameterJdbcOperationsDependencyConfiguration
390+
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
391+
392+
public FlywayNamedParameterJdbcOperationsDependencyConfiguration() {
393+
super("flyway");
394+
}
395+
396+
}
397+
362398
private static class LocationResolver {
363399

364400
private static final String VENDOR_PLACEHOLDER = "{vendor}";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.jdbc;
18+
19+
import org.springframework.beans.factory.config.BeanDefinition;
20+
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
21+
import org.springframework.boot.autoconfigure.AbstractDependsOnBeanFactoryPostProcessor;
22+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
23+
24+
/**
25+
* {@link BeanFactoryPostProcessor} that can be used to dynamically declare that all
26+
* {@link NamedParameterJdbcOperations} beans should "depend on" one or more specific
27+
* beans.
28+
*
29+
* @author Dan Zheng
30+
* @since 2.1.x
31+
* @see BeanDefinition#setDependsOn(String[])
32+
*/
33+
public class NamedParameterJdbcOperationsDependsOnPostProcessor
34+
extends AbstractDependsOnBeanFactoryPostProcessor {
35+
36+
public NamedParameterJdbcOperationsDependsOnPostProcessor(String... dependsOn) {
37+
super(NamedParameterJdbcOperations.class, dependsOn);
38+
}
39+
40+
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/liquibase/LiquibaseAutoConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
3737
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
3838
import org.springframework.boot.autoconfigure.jdbc.JdbcOperationsDependsOnPostProcessor;
39+
import org.springframework.boot.autoconfigure.jdbc.NamedParameterJdbcOperationsDependsOnPostProcessor;
3940
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
4041
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4142
import org.springframework.boot.jdbc.DataSourceBuilder;
@@ -45,6 +46,7 @@
4546
import org.springframework.core.io.Resource;
4647
import org.springframework.core.io.ResourceLoader;
4748
import org.springframework.jdbc.core.JdbcOperations;
49+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
4850
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
4951
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
5052
import org.springframework.util.Assert;
@@ -58,6 +60,7 @@
5860
* @author Eddú Meléndez
5961
* @author Andy Wilkinson
6062
* @author Dominic Gunn
63+
* @author Dan Zheng
6164
* @since 1.1.0
6265
*/
6366
@Configuration
@@ -208,4 +211,20 @@ public LiquibaseJdbcOperationsDependencyConfiguration() {
208211

209212
}
210213

214+
/**
215+
* Additional configuration to ensure that {@link NamedParameterJdbcOperations} beans
216+
* depend on the liquibase bean.
217+
*/
218+
@Configuration
219+
@ConditionalOnClass(NamedParameterJdbcOperations.class)
220+
@ConditionalOnBean(NamedParameterJdbcOperations.class)
221+
protected static class LiquibaseNamedParameterJdbcOperationsDependencyConfiguration
222+
extends NamedParameterJdbcOperationsDependsOnPostProcessor {
223+
224+
public LiquibaseNamedParameterJdbcOperationsDependencyConfiguration() {
225+
super("liquibase");
226+
}
227+
228+
}
229+
211230
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/JdbcTemplateAutoConfigurationTests.java

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@
1616

1717
package org.springframework.boot.autoconfigure.jdbc;
1818

19+
import java.util.HashMap;
20+
import java.util.Map;
21+
1922
import javax.sql.DataSource;
2023

2124
import org.junit.Test;
2225

26+
import org.springframework.beans.BeansException;
27+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
28+
import org.springframework.beans.factory.config.BeanDefinition;
29+
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
30+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
31+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
2332
import org.springframework.boot.autoconfigure.AutoConfigurations;
33+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
34+
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
35+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
2437
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration;
2538
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
39+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2640
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2741
import org.springframework.context.annotation.Bean;
2842
import org.springframework.context.annotation.Configuration;
@@ -33,6 +47,7 @@
3347
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
3448

3549
import static org.assertj.core.api.Assertions.assertThat;
50+
import static org.junit.Assert.fail;
3651
import static org.mockito.Mockito.mock;
3752

3853
/**
@@ -41,6 +56,7 @@
4156
* @author Dave Syer
4257
* @author Stephane Nicoll
4358
* @author Kazuki Shimizu
59+
* @author Dan Zheng
4460
*/
4561
public class JdbcTemplateAutoConfigurationTests {
4662

@@ -185,6 +201,52 @@ public void testDependencyToFlyway() {
185201
});
186202
}
187203

204+
@Test
205+
public void testDependencyToFlywayWithJdbcTemplateMixed() {
206+
this.contextRunner
207+
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
208+
.withPropertyValues("spring.flyway.locations:classpath:db/city_np")
209+
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
210+
.run((context) -> {
211+
assertThat(context).hasNotFailed();
212+
assertThat(context.getBean(JdbcTemplate.class)).isNotNull();
213+
assertThat(context.getBean(
214+
NamedParameterDataSourceMigrationValidator.class).count)
215+
.isEqualTo(1);
216+
});
217+
}
218+
219+
@Test
220+
public void testDependencyToFlywayWithOnlyNamedParameterJdbcTemplate() {
221+
ApplicationContextRunner contextRunner1 = new ApplicationContextRunner()
222+
.withPropertyValues("spring.datasource.initialization-mode=never",
223+
"spring.datasource.generate-unique-name=true")
224+
.withConfiguration(
225+
AutoConfigurations.of(DataSourceAutoConfiguration.class,
226+
JdbcTemplateAutoConfiguration.class,
227+
OnlyNamedParameterJdbcTemplateAutoConfiguration.class));
228+
contextRunner1
229+
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
230+
.withPropertyValues("spring.flyway.locations:classpath:db/city_np")
231+
.withConfiguration(AutoConfigurations.of(FlywayAutoConfiguration.class))
232+
.run((context) -> {
233+
assertThat(context).hasNotFailed();
234+
assertThat(context.containsBean("jdbcTemplate")).isFalse();
235+
try {
236+
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
237+
fail("org.springframework.boot.autoconfigure.jdbc.JdcTemplate should not exist in the application context");
238+
}
239+
catch (NoSuchBeanDefinitionException ex) {
240+
241+
}
242+
assertThat(context.getBean(NamedParameterJdbcTemplate.class))
243+
.isNotNull();
244+
assertThat(context.getBean(
245+
NamedParameterDataSourceMigrationValidator.class).count)
246+
.isEqualTo(1);
247+
});
248+
}
249+
188250
@Test
189251
public void testDependencyToLiquibase() {
190252
this.contextRunner.withUserConfiguration(DataSourceMigrationValidator.class)
@@ -199,6 +261,50 @@ public void testDependencyToLiquibase() {
199261
});
200262
}
201263

264+
@Test
265+
public void testDependencyToLiquibaseWithJdbcTemplateMixed() {
266+
this.contextRunner
267+
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
268+
.withPropertyValues(
269+
"spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city-np.yaml")
270+
.withConfiguration(
271+
AutoConfigurations.of(LiquibaseAutoConfiguration.class))
272+
.run((context) -> {
273+
assertThat(context).hasNotFailed();
274+
assertThat(context.getBean(JdbcTemplate.class)).isNotNull();
275+
assertThat(context.getBean(
276+
NamedParameterDataSourceMigrationValidator.class).count)
277+
.isEqualTo(1);
278+
});
279+
}
280+
281+
@Test
282+
public void testDependencyToLiquibaseWithOnlyNamedParameterJdbcTemplate() {
283+
this.contextRunner
284+
.withUserConfiguration(NamedParameterDataSourceMigrationValidator.class)
285+
.withPropertyValues(
286+
"spring.liquibase.changeLog:classpath:db/changelog/db.changelog-city-np.yaml")
287+
.withConfiguration(AutoConfigurations.of(
288+
OnlyNamedParameterJdbcTemplateAutoConfiguration.class,
289+
LiquibaseAutoConfiguration.class))
290+
.run((context) -> {
291+
assertThat(context).hasNotFailed();
292+
assertThat(context.containsBean("jdbcTemplate")).isFalse();
293+
try {
294+
JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);
295+
fail("org.springframework.boot.autoconfigure.jdbc.JdcTemplate should not exist in the application context");
296+
}
297+
catch (NoSuchBeanDefinitionException ex) {
298+
299+
}
300+
assertThat(context.getBean(NamedParameterJdbcTemplate.class))
301+
.isNotNull();
302+
assertThat(context.getBean(
303+
NamedParameterDataSourceMigrationValidator.class).count)
304+
.isEqualTo(1);
305+
});
306+
}
307+
202308
@Configuration
203309
static class CustomConfiguration {
204310

@@ -278,4 +384,66 @@ static class DataSourceMigrationValidator {
278384

279385
}
280386

387+
static class NamedParameterDataSourceMigrationValidator {
388+
389+
private final Integer count;
390+
391+
NamedParameterDataSourceMigrationValidator(
392+
NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
393+
String sql = "SELECT COUNT(*) from CITY WHERE id = :id";
394+
Map<String, Long> param = new HashMap<>();
395+
param.put("id", 1L);
396+
this.count = namedParameterJdbcTemplate.queryForObject(sql, param,
397+
Integer.class);
398+
}
399+
400+
}
401+
402+
@Configuration
403+
@ConditionalOnClass({ DataSource.class })
404+
@ConditionalOnSingleCandidate(DataSource.class)
405+
@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
406+
JdbcTemplateAutoConfiguration.class })
407+
@AutoConfigureBefore({ FlywayAutoConfiguration.class,
408+
LiquibaseAutoConfiguration.class })
409+
@EnableConfigurationProperties(JdbcProperties.class)
410+
static class OnlyNamedParameterJdbcTemplateAutoConfiguration
411+
implements BeanDefinitionRegistryPostProcessor {
412+
413+
@Bean
414+
public NamedParameterJdbcTemplate myNamedParameterJdbcTemplate(
415+
DataSource dataSource) {
416+
return new NamedParameterJdbcTemplate(dataSource);
417+
}
418+
419+
@Override
420+
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
421+
throws BeansException {
422+
// do nothing
423+
}
424+
425+
/**
426+
* <p>
427+
* we should remove the jdbc template bean definition to keep only
428+
* NamedParameterJdbcTemplate is registerd in the bean container
429+
* </p>
430+
* @param registry the bean definition registry.
431+
* @throws BeansException if the bean registry have any exception.
432+
*/
433+
@Override
434+
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
435+
throws BeansException {
436+
String[] excludeBeanNames = new String[] { "jdbcTemplate",
437+
"namedParameterJdbcTemplate" };
438+
for (String beanName : excludeBeanNames) {
439+
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
440+
if (beanDefinition != null) {
441+
registry.removeBeanDefinition(beanName);
442+
}
443+
}
444+
445+
}
446+
447+
}
448+
281449
}

0 commit comments

Comments
 (0)