Automatic database upgrades using liquibase

Liquibase seems like a great replacement for the database scripts which perform initial installation and migration between versions of an application.  This is especially true when your application can be run on multiple database platforms (meaning a separate set of database scripts for each platform).  


The only thing that I don’t like is that the change logs that should be run will vary depending on if it is a new installation, upgrade from version A, upgrade from version B, etc.  The need for the different change logs is illustrated in the tutorial using Oracle.  It seems that the only reason this is required is that the preconditions for a change log are limited to HALT and WARN.


I think that it would be incredibly useful to be able to have a single change log which includes a list of other change logs.  The first change log included will perform the initial installation of the database objects (could be the last major version).  The change log for the initial installation would have a precondition that would prevent it from running if the db objects already exist (the logic could vary here).  The other change logs (maybe just one - perhaps multiple) would apply updates for each subsequent version.  Just like the installation change log, the ones which apply the updates would have a precondition which would skip the updates if they have already been applied.


The general idea here is that there is a single command (using ant, maven, or Spring bean) that could upgrade the database from any of the various states to the one required for the current version of the application.  This eliminates the need for documentation included with every release of the application describing the commands to install or upgrade depending on the currently installed version.  


Granted, in the process described in the Oracle tutorial, it is recommended to only upgrade to the next major release of the application.  For example, to upgrade from 1.0 to 3.0, you would first upgrade to 2.0.  This effectively allows you to run the same command during the 2.0 upgrade and the 3.0 upgrade (same process but different change log).  However, there are cases where upgrading from a minor or patch release could require a different command.


In my specific use case, I have a web application that uses Spring.  I want to be able to ship a new version of the application to my customer without any instructions on how to upgrade the database prior to deploying the new version.  The Spring bean should be able to execute the correct change log(s) to upgrade from any version.  The problem is that when the precondition of a change log fails a ValidationFailedException is thrown which causes the Spring to fail.


I have been able to work around this issue by creating a subclass of SpringLiquibase and overriding the afterPropertiesSet() method.  If there was a precondition failure, I just write out a log message and suppress the exception.  


Once the ValidationFailedException is thrown, the Spring bean supplied with Liquibase will not execute any other change logs.  To get around this, I have multiple beans in my application context each executing one change log.  Using the depends-on tag for each of the bean, I can set the order in which they are executed.  


The end result is a Spring configuration that enables a fully automatic installation or upgrade.  It can also handle upgrade from any past version without having to install any intermediate versions.  It can also detect a version of the application that was deployed before implementing Liquibase.  In this case, it just updates the databasechangelog table to set the version.


Spring config:


 

   

 


 

   

 


 

   

 


 

   

 


Subclass of SpringLiquibase:


public class SpringWrapper extends SpringLiquibase {

  private static final Logger log = Logger.getLogger(SpringWrapper.class);


  @Override

  public void afterPropertiesSet() throws LiquibaseException {

    try {

      super.afterPropertiesSet();

    } catch (ValidationFailedException e) {

      if (!isFatal(e)) {

        log.info("Skipping changelog " + getChangeLog() + " due to precondition failures - " + e.getMessage());

      }

    }

  }


  private boolean isFatal(final ValidationFailedException e) {

    List failedPreconditions = extractFailedPreConditions(e);

    boolean result = false;

    if (failedPreconditions != null && failedPreconditions.size() == 0) {

      result = true;

    }

    return result;

  }


  private List extractFailedPreConditions(final ValidationFailedException e) {

    /* Do not look at the code in this method!!! */

    Field field = ReflectionUtils.findField(e.getClass(), “failedPreconditions”);

    ReflectionUtils.makeAccessible(field);

    return (List) ReflectionUtils.getField(field, e);

  }

}


Disclaimer: This code has NOT been well tested.


While the solution that I have implemented works, I would much rather have a single spring bean executing a single change log.  For this to work, Liquibase would need to be able to skip a change log if a precondition fails.  


From what I have read in the forum, it appears that the only other solution for this is to add these preconditions to each changeset.  Has anyone else been able to solve this issue in a different way?

I would say that the oracle tutorial is a way people can use to organize their changesets, but by no means the standard (or even recommended in may cases). It is mainly there to give an alternate example. Take what you want from it, and ignore what you don’t like in it. Personally, I use the single changelog style because it doesn’t really matter if you are skipping versions or going one at a time, the goal of liquibase is to get the database to the version that the current application supports.


The only problem (like you mention) is that not including a bootstrap changelog file for supporting pre-liquibase databases is not easy currently. I added http://liquibase.jira.com/browse/CORE-917 to track adding support for that. It will probably not be too bad to do and could be done for 2.1 I think.


Nathan



Handling rollbacks with this approach would be no different than for any other approach.  You can still use the command line interface to rollback the changes.  If you are currently rolling back based on date/time, you would need use tags instead.  


In my organization, our procedure from recovering from a failed upgrade is to restore the database.  Even if you have complete trust in liquibase to perform rollbacks correctly, there are many instances where the author of the change set must do something to enable to rollback (reversing data manipulation is an example).  Unless you have carefully tested the rollbacks for each release, it is quite dangerous.  It is my opinion that the time spent testing rollbacks is better spent testing upgrades against a copy of production before upgrading.


Note: On some databases (like Oracle), you can perform a point in time recovery.  Basically, the database will reverse the changes made since a certain point in time.  It is much faster than a full restore.

What about rollbacks in case of errors? how does this approach handle that.
Suppose u upgraded from version 1 to 2 to 3 and saw a big problem with version 3 and want to rollback to version 2 how do u handle that?