diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java index e8f76ffe121..a2ff0e4994c 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializer.java @@ -16,8 +16,8 @@ package org.springframework.boot.autoconfigure.jdbc; -import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.annotation.PostConstruct; @@ -30,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; +import org.springframework.jdbc.config.SortedResourcesFactoryBean; import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.util.StringUtils; @@ -129,21 +130,27 @@ class DataSourceInitializer implements ApplicationListener getResources(String locations) { - List resources = new ArrayList(); - for (String location : StringUtils.commaDelimitedListToStringArray(locations)) { - try { - for (Resource resource : this.applicationContext.getResources(location)) { - if (resource.exists()) { - resources.add(resource); - } + return getResources( + Arrays.asList(StringUtils.commaDelimitedListToStringArray(locations))); + } + + private List getResources(List locations) { + SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean( + this.applicationContext, locations); + try { + factory.afterPropertiesSet(); + List resources = new ArrayList(); + for (Resource resource : factory.getObject()) { + if (resource.exists()) { + resources.add(resource); } } - catch (IOException ex) { - throw new IllegalStateException( - "Unable to load resource from " + location, ex); - } + return resources; + } + catch (Exception ex) { + throw new IllegalStateException("Unable to load resources from " + locations, + ex); } - return resources; } private void runScripts(List resources, String username, String password) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java index 205fb015b17..d8116efffe2 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerTests.java @@ -16,7 +16,10 @@ package org.springframework.boot.autoconfigure.jdbc; +import java.io.IOException; import java.sql.SQLException; +import java.util.Arrays; +import java.util.Comparator; import java.util.Random; import javax.sql.DataSource; @@ -34,6 +37,11 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; @@ -181,15 +189,11 @@ public class DataSourceInitializerTests { this.context.register(DataSourceAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); - DataSource dataSource = this.context.getBean(DataSource.class); - this.context.publishEvent(new DataSourceInitializedEvent(dataSource)); - assertThat(dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource).isTrue(); assertThat(dataSource).isNotNull(); JdbcOperations template = new JdbcTemplate(dataSource); - try { template.queryForObject("SELECT COUNT(*) from BAR", Integer.class); fail("Query should have failed as BAR table does not exist"); @@ -245,6 +249,28 @@ public class DataSourceInitializerTests { } } + @Test + public void multipleScriptsAppliedInLexicalOrder() throws Exception { + EnvironmentTestUtils.addEnvironment(this.context, + "spring.datasource.initialize:true", + "spring.datasource.schema:" + ClassUtils + .addResourcePathToPackagePath(getClass(), "lexical-schema-*.sql"), + "spring.datasource.data:" + ClassUtils + .addResourcePathToPackagePath(getClass(), "data.sql")); + this.context.register(DataSourceAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + ReverseOrderResourceLoader resourceLoader = new ReverseOrderResourceLoader( + new DefaultResourceLoader()); + this.context.setResourceLoader(resourceLoader); + this.context.refresh(); + DataSource dataSource = this.context.getBean(DataSource.class); + assertThat(dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource).isTrue(); + assertThat(dataSource).isNotNull(); + JdbcOperations template = new JdbcTemplate(dataSource); + assertThat(template.queryForObject("SELECT COUNT(*) from FOO", Integer.class)) + .isEqualTo(1); + } + @Configuration @EnableConfigurationProperties protected static class TwoDataSources { @@ -264,4 +290,42 @@ public class DataSourceInitializerTests { } + /** + * {@link ResourcePatternResolver} used to ensure consistently wrong resource + * ordering. + */ + private static class ReverseOrderResourceLoader implements ResourcePatternResolver { + + private final ResourcePatternResolver resolver; + + ReverseOrderResourceLoader(ResourceLoader loader) { + this.resolver = ResourcePatternUtils.getResourcePatternResolver(loader); + } + + @Override + public Resource getResource(String location) { + return this.resolver.getResource(location); + } + + @Override + public ClassLoader getClassLoader() { + return this.resolver.getClassLoader(); + } + + @Override + public Resource[] getResources(String locationPattern) throws IOException { + Resource[] resources = this.resolver.getResources(locationPattern); + Arrays.sort(resources, new Comparator() { + + @Override + public int compare(Resource r1, Resource r2) { + return r2.getFilename().compareTo(r1.getFilename()); + } + + }); + return resources; + } + + } + } diff --git a/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-aaa.sql b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-aaa.sql new file mode 100644 index 00000000000..ff27e5da80e --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-aaa.sql @@ -0,0 +1,4 @@ +CREATE TABLE FOO ( + id INTEGER IDENTITY PRIMARY KEY, + todrop VARCHAR(30), +); diff --git a/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-bbb.sql b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-bbb.sql new file mode 100644 index 00000000000..bd6b7221ec5 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/resources/org/springframework/boot/autoconfigure/jdbc/lexical-schema-bbb.sql @@ -0,0 +1,2 @@ +ALTER TABLE FOO DROP COLUMN todrop; +ALTER TABLE FOO ADD COLUMN name VARCHAR(30);