LiquiBase-ifying your Grails Application
At 2Paths we’ve got some pretty good processes in place: we practice agile software development and scrum, have all our projects set up in Continuous Integration. We try to do test-driven development where at all possible. One area that has slipped through the cracks though is database change management. What company hasn’t run into the problem of ensuring all database environments in a project (dev, staging, production, test) are in sync and in source control? Without this, it becomes an onerous if not impossible task to rollback a set of databases to a known state, or to recreate one from scratch to a known state.
Enter LiquiBase, an open source database-agnostic tool for tracking, managing and applying database changes. We recently worked on a small Grails project and decided to give this tool a try and I was duly impressed.
LiquiBase functionality is built around a main changelog.xml file containing changesets representing incremental database changes to be applied to a database. LiquiBase manages which changesets have been run through a DATABASECHANGELOG table it creates in each database.
We used the grails plugin which gave us most of the full LiquiBase functionality, albeit a little less than mature. There were a couple of gotchas and bugs with the plugin, but nothing we couldn’t work around.
LiquiBase buys us the ability to store database change in source control, easily sync databases in multi-environments in an automated and controlled fashion, tag database states upon iteration-end releases, auto-generate rollback sql to tagged states, diff databases, and much more.
LiquiBase-ifying Your Application
With your database in a known state, you can use the grails plugin to create your changelog.xml. First you need to install the grails LiquiBase plugin:
grails install-plugin liquibase
Once installed simply run this from the root of your grails app:
grails generate-changelog grails-app/changelog.xml
This will generate the changelog.xml from your development database (specified in DataSource.groovy) and write it to the path specified in the command. grails-app/changelog.xml is the default path where the grails plugin will be expecting the changelog to be. If you need to read from a different database environment like staging for example, just add the -Dgrails.env=staging option to the command.
To propagate this changelog to other databases (say, test), run
grails -Dgrails.env=test migrate. This runs all the sql necessary to upate the database to match the changelog. Any new changes from here on will be appended as new changesets, and can be migrated with the same command as above. You can also use the migrate-sql command instead if want to generate the sql and run it yourself. Most LiquiBase commands come in tuples: one to generate the sql for you and one to just run the sql directly against your database.
If you’re integrating LiquiBase mid-project and already have all your databases set up, you’ll need to run sql against them to update the LiquiBase DATABASECHANGELOG table to show all the changes as run. First, make sure that the databases are all in sync. To do this, you can use the handy LiquiBase diff tool. Unfortunately, the grails plugin for this diff tool is not very robust and will only diff your development database against your test database – the plugin has those two environments hard-coded. You can either mess with your DataSource.groovy db environments to do the diff setting the two dbs in question to dev and test temporarily, or install LiquiBase itself and run the diff passing the db parameters to the diff command. Using the grails plugin you would run:
grails db-diff which will spit to the screen any differences as changesets to be applied. Strangely, there is no documentation for this command in the LiquiBase Grails plugin page.
Once your database are in sync, run
grails changelog-sync-sql with the appropriate -Dgrails.env switch to generate the sql to update the DATABASECHANGELOG table, then run the sql in your database.
LiquiBase and Continuous Integration
We use Hudson at 2Paths for our CI, and have added the simple grails -Dgrails.env=test migrate command as part of our build process to migrate the test database upon every checkin. This ensures that the test db is always the most up-to-date.
Rolling LiquiBase into our Dev Process
We’ve adopted on a trial basis the following process for database change management as part of our agile software development, taking into consideration Grails development (which uses hibernate) which can auto-generates schemas based on domain objects:
- Generate changelog from initial schema and commit to svn
- Rollout schema to other databases by migrating from changelog
- if the schema already exists and it needs “LiquiBase-ifying”, generate changelog-sync-sql and run it
- Add every schema change via new changeset from hereon in by generating changelogs via grails db-diff
- add / change domain objects in project
- set hiberante ddl mode to update
- start app and grails will automatically update the db
- generate new changelogs using db-diff (against the test db)
- append changelogs to changelog.xml
- run
grails changelog-sync-sqlto generate the sql that will mark all these new changes as ran on your db, then run the sql - checkin changes. this will be applied to test
- provide explicit rollback sql for any custom sql
- tag each iteration end with a “tagDatabase” changelog in the changelog.xml
Gotchas
We ran into some gotchas with LiquiBase. One of the first things we started doing before fully understanding how LiquiBase worked was to update existing changesets when changing the schema. LiquiBase generates checksums for each changeset to ensure they don’t change, and altering existing changelogs will cause future migrations to fail. Even though LiquiBase gives you some tools to get around this, it’s generally a better practice to just add a new changeset for every database change. There is a good blog on the LiquiBase site explaining how to deal with changing changesets.
Another gotcha is that the rollback-sql command won’t magically generate rollback scripts for hand-coded sql (obviously!) You must to generate your own rollback sql for these custom sql tags, otherwise not only will you not get rollback sql for those particular changesets, but the rollback functionality won’t spit out sql for any other changeset either until you do.
Tagging was a little fussy as well. The command line grails tag can only tag a given state once – future tags will overwrite the earlier ones unless you’re at a different changeset. I found it better to add a tag changeset explicitly in the changelog.xml. The Grails plugin for tagging also seems to be problematic with spaces in the tag name. We decided to just use underscores as a convention.
We also ran across some broken functionality:
- The
grails generate-changelogcommand generates some extraneous information that breaks sql for data type numeric(19,0) if that type has auto-increment on. It generates this in the changelog:
<column autoIncrement=”true” name=”id” type=”numeric()(19,0)”> (notice the extra empty brackets). We needed to manually remove these to get it to work. - The
grails rollback-to-date-sqlcommand writes to a file with the datestamp instead of to console like the other rollbacks do, even though the docs say it writes to STDOUT (rollback-count-sqlalso has issues, butrollback-sqlfor tags works just fine). The code for these plugin commands doesn’t seem to do the right thing with the args. Time permitting, we may provide a patch for this. - db-doc generation doesn’t work with large amount of columns in a table because it uses the table description including column names for the html file name. Too many db columns make the file names too long.
Moving Forward
Although there’s a bit of grumbling amongst the developers here that using LiquiBase with the above process is a little onerous, it buys us a lot more certainty for knowing what state our databases are in, making sure changes are in source control, and knowing what updates have or haven’t been run. It’s a whole lot easier than hand-coding a bunch of sql, and easily accommodates the possibility of migrating to other DBMS’s in the future without having to re-write DBMS-specific sql. There’s even talk of trying to go a step or two further to automate even more of the process so a developer doesn’t have to think about generating changesets upon changing of a grails domain model. We’ll see how that goes.

June 8th, 2009 at 08:08:41
Thanks, your article helped me a lot ! It should be #1 in Google
October 2nd, 2009 at 09:58:08
Will LiquiBase work if there are a number of different installed databases? So say I have two clients, one is running my grails app using PostgreSQL and the other is running my grails app using MySQL, and I upgrade the app from 0.2 to 0.3. There are database changes involved and I want the system to automatically migrate my clients from 0.2 to 0.3 when they run the new war file.
Do I get this with Liquibase? Is there a way to get this using Liquibase or some other product?
Thank you!
October 2nd, 2009 at 16:53:32
Hi Ken, I’m not sure I completely understand your setup. Are you saying you have one grails app that is deployed out to two different environments, one using PostgreSQL and one using MySQL? If so, Liquibase is ideal for this.
You would simply have different grails environments set up for each of these environments, and run the “grails migrate” command for each of the environments coordinated with when you deploy your war. This would be an added step to run: it’s not rolled out automatically as part of the war extraction.
An example (as I showed above) for running this in a specific environment, say “test”, would be “grails -Dgrails.env=test migrate”. For information on grails environments see http://grails.org/doc/1.1.x/guide/single.html#3.2%20Environments.
Let me know if I understood your question correctly and if this helps.
October 6th, 2009 at 16:43:09
Yes, that clears it up.
I was hoping for a solution that would allow a customer to automatically update their database schema on installation of a new war, but, to be honest, I don’t really need that feature now.
Thank you!
November 23rd, 2010 at 14:19:22
Hello,
thank you for the article, but I’m still a bit lost as to the benefit of using this with grails.
If grails is using hibernate to generate the tables from the domain objects, why is this necessary at all then?
When I make a change to an object and run it, the changes are made in the db automatically. Using SVN to commit and share changes, it seems like all the dev dbs would be in sync.
As long as I am not making changes to non-native grails tables, then this seems pointless, but I am sure I am missing something.
If you can describe another use case for this while developing with Grails, that would be helpful.
thanks.
November 24th, 2010 at 22:31:06
Hi Steve, yes – all your *dev* dbs would be in sync by using SVN to commit and share changes. Where LiquiBase becomes very useful is when rolling out the changes to staging and production environments. We have a policy here to never leave a production grails app in hibernate “update” mode, as when there is production data involved, we certainly don’t want to corrupt or lose any data. We also don’t always have access rights to do schema changes from the application on production databases anyway.
Many of our clients host their own applications we have created, and many have very strict control over their databases. For example, an iteration deploy from us would typically involve us sending over a war file and accompanying sql file for their DBA to run against the production database. The DBA would scrutinize the changes in the sql file prior to running it in parallel to the deployment of the war file.
Also because some of our applications are running against huge legacy databases, the creation of new non-null fields on a domain object sometimes need to be populated by complex sql against non-application tables elsewhere in the database. This all needs to be done while the application is offline, and via straight sql. LiquiBase can track this custom sql as I mentioned in my blog.
Please let me know if this helps.