June 13, 2019

Liquibase has a very useful feature when using it in a Spring Boot multi-module project: includeAll. Unfortunatly it’s not working properly.

Update December 2020

The Liquibase PR 1140 has been merged into 3.10.x branch. Starting with 2.3.7, the new version is also integrated into Spring Boot - everything should now work as expected!

If you would like to initialize a Spring Boot project with custom database schema, multi-module option and Liquibase scripts, check out Bootify.io - create your first prototype in no time.

Backgrounds

IncludeAll should detect all changelog files matching the given pattern, for example:

1
2
3
databaseChangeLog:
  - includeAll:
      path: classpath*:/changelogs

However, when the changelogs are distributed over several modules as part of a Spring Boot fat jar, not all of them are picked up.

This issue has been discussed already, but is still not fixed (Liquibase 3.6.3). It looks like it’s coming from the line getResources(""), which is supposed to find the root path of every jar file in the project, but is incomplete when running in a fat jar. So instead one could try to get the base paths with another resource lookup, searching for all xml and yaml files in the project. As every relevant jar contains a changelog file with such an ending, the list should be complete.

Workaround

Until a proper bugfix is released, I suggest the following workaround.

  1. Create a copy of SpringLiquibase.java contained in the Liquibase library you are working with. Keep exactly the same package (liquibase.integration.spring), so this class will take precedence over the original one when being picked up by the classloader.

  2. Add the following snippet into the SpringResourceOpener class. Call the new method as part of createResourceOpener().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    public void addSpringBootJars(ResourceLoader resourceLoader) {
        ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
        for (String ending : new String[] {"xml", "yml", "yaml"}) {
            Resource[] resources = resolver.getResources("classpath*:**/*." + ending);
            for (Resource resource : resources) {
                String url = resource.getURL().toExternalForm();
                if (!url.startsWith("jar:")) {
                    continue;
                }
                url = url.substring(4, url.indexOf('!'));
                url = url.replace("\\", "/");
                if (url.startsWith("file:") && url.charAt(5) != '/') {
                    url = "file:/" + url.substring(5) + "/";
                }
                if (!getRootPaths().contains(url)) {
                    addRootPath(new URL(url));
                }
            }
        }
    }

Now all relevant jars of your Spring Boot project should be found - containing a changelog file ending with xml, yml or yaml! Let me know if you have any further suggestions.

Contact