Liquibase execution order of changeset files when using includeAll with classpath*:

巧了我就是萌 提交于 2019-12-06 13:14:29

You are right, Liquibase is relying on the underlying "list files" logic which orders files alphabetically through the file system but apparently does not through classpaths.

I created https://liquibase.jira.com/browse/CORE-1843 to track the fix.

For now, if you configure spring with a subclass of liquibase.integration.spring.SpringLiquibase that overrides getResources(String packageName) with a method that sorts the returned Enumeration that should resolve the problem for you.

So after some thinking and one night of sleep I came up with the following hack to guarantee order of the loaded changelog files via classpath pattern classpath*:/my/path/to/changelog/*.xml . The idea is to create the main changelog file on the fly via dom manipulation, when liquibase requests it.

It only works for the main changelog file. Following prerequisite:

  • The pattern can only be used for the main changelog file
  • I use an empty master changelog file as template
  • All other changelog files have to use the normal allowed loading mechanism
  • Works only in an Spring environment

First I had to extend/overwrite the liquibase.integration.spring.SpringLiquibase with my implementation.

public class MySpringLiquibase extends SpringLiquibase {

  private static final Logger logger = LoggerFactory.getLogger(MySpringLiquibase.class);

  private ApplicationContext context;

  private String changeLogLocationPattern;

  private List<String> changeLogLocations;

  @Autowired
  public void setContext(ApplicationContext context) {
    this.context = context;
  }

  /**
   * Location pattern to search for changelog files.
   * 
   * @param changeLogLocationPattern
   */
  public void setChangeLogLocationPattern(String changeLogLocationPattern) {
    this.changeLogLocationPattern = changeLogLocationPattern;
  }

  @Override
  public void afterPropertiesSet() throws LiquibaseException {
    try {
      changeLogLocations = new ArrayList<String>();
      // retrieve all changelog resources for the pattern
      List<Resource> changeLogResources = Arrays.asList(context.getResources(changeLogLocationPattern));
      for (Resource changeLogResource : changeLogResources) {
        // get only the classpath path of the resource
        String changeLogLocation = changeLogResource.getURL().getPath();
        changeLogLocation = "classpath:" + StringUtils.substringAfterLast(changeLogLocation, "!");
        changeLogLocations.add(changeLogLocation);
    }
    // sort all found resources by string
    Collections.sort(changeLogLocations, String.CASE_INSENSITIVE_ORDER);
  } catch (IOException e) {
    throw new LiquibaseException("Could not resolve changeLogLocationPattern", e);
  }
  super.afterPropertiesSet();
}

@Override
protected SpringResourceOpener createResourceOpener() {

  final String mainChangeLog = getChangeLog();

  return new SpringResourceOpener(getChangeLog()) {

  @Override
  public InputStream getResourceAsStream(String file)
    throws IOException {
    // check if main changelog file
    if(mainChangeLog.equals(file)) {
      // load master template and convert to dom object
      Resource masterResource = getResourceLoader().getResource(file);
      Document masterDocument = DomUtils.parse(masterResource, true);
      // add all changelog locations as include elements
      for (String changeLogLocation : changeLogLocations) {
        Element inlcudeElement = masterDocument.createElement("include");
        inlcudeElement.setAttribute("file", changeLogLocation);
        masterDocument.getDocumentElement().appendChild(inlcudeElement);
      }
      if(logger.isDebugEnabled()) {
        logger.debug("Master changeset: {}", DomUtils.toString(masterDocument));
      }
      // convert dom back to string and give it back as input resource
      return new ByteArrayInputStream(DomUtils.toBytes(masterDocument));
    } else {
      return super.getResourceAsStream(file);
    }
  }

};
}

}

This class now needs to be used in the spring xml configuration.

    <bean id="liquibase" class="liquibase.integration.spring.MySpringLiquibase"
      p:changeLog="classpath:/plugin/liquibase/master.xml"
      p:dataSource-ref="dataSource"
      p:contexts="${liquibase.contexts:prod}"
      p:ignoreClasspathPrefix="true"
      p:changeLogLocationPattern="classpath*:/plugin/liquibase/changes/*.xml"/>

With this changes I have achieved that my main changelog files are ordered by their name.

Hope that helps others too.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!