Countdown to Grails 2.0: Database Migrations |
|
One of the many nice features of Grails is the way it will automatically create your database schema for you from your domain model. Admittedly it's a feature of Hibernate that Grails uses, but still, it helps you get started very quickly with database-driven web applications without having to worry about the database schema.
What happens once your application moves to production? During development, losing the data in between server runs isn't a big issue. But you can't just drop the database in production. So that rules out the "create" and "create-drop" values for the dbCreate data source setting. What about "update"? It won't clear the data from your database, so it's often used by people. Yet it doesn't work well for production because it has significant limitations. For example, it can't handle simple column renames and it certainly can't handle modifications to existing data that might be a necessary part of an upgrade. Although it's tempting to use dbCreate = "update" for your deployments to production, it is usually the wrong solution.
What does that leave you with? SQL scripts for performing the migrations? That's certainly possible, but it's not easy to create the appropriate SQL for given changes in your GORM domain model. And it's not suitable if you need to support multiple database types, since the SQL will probably differ for each one.
Fortunately, there is a flexible, database-agnostic tool for performing schema migrations: Liquibase. Even better, there are two plugins for Grails that make using Liquibase a bit easier than would otherwise be the case: the Liquibase plugin and Autobase.
So if those plugins have been around for a long time, what's new for Grails 2.0? Database migrations are an essential part of using Grails for any serious work and so the Grails team decided that there should be an official way to handle them. The result is a new plugin that combines the best parts of the Liquibase and Autobase plugins: the Database Migration plugin.
In brief
Even though core database migration support was part of the Grails 2.0 roadmap, there was no reason to tie it to that version. That means you can use the plugin on Grails 1.3 projects as well. The plugin brings these features to your applications:
- Declarative database schema and data migration
- Groovy and XML migration scripts
- Manual or automatic execution of migrations
- Automatic tracking of migrations that have already been applied
- Generation of migrations by diffing domain model and database
It provides well over 20 commands, giving you plenty of control. You can find out more from the plugin's user guide, but put simply it provides a great deal of help for managing controlled upgrades of a database for new versions of a Grails application. You can still use the other plugins or even a completely different approach if you want, but for most users this is definitely the way to go.
The rest of this article will show you a common usage pattern for the plugin.
Getting started
Imagine you have been working a Grails application and now you want to deploy the first version to production. It's time to consider how you're going to manage database upgrades. At this point the production database hasn't even been created. So, declare the Database Migration plugin as a runtime dependency like so:
grails.project.dependency.resolution = {
inherits "global"
...
plugins {
runtime ":database-migration:1.0"
compile ":hibernate:$grailsVersion"
compile ":jquery:1.6.1.1"
compile ":resources:1.0.2"
build ":tomcat:$grailsVersion"
}
...
}
and run grails compile. At the time of writing, 1.0 is the latest version of the plugin. Check the Grails plugin portal to find out what the latest version is at any given time.
With the plugin available, you can start the database migration journey. As I said, the production database hasn't been created yet. You could deploy the initial version of the application with a dbCreate value of "update" and that would work fine. But for reasons that I'll discuss later, I want to encourage you to initialise the database from a Database Migration changelog, i.e. a migration script. Don't worry, it's a lot less work than you might think.
The trick is to use one of the plugin's commands to generate the migration scripts for us. You start by making sure that your 'prod' database is empty and removing any dbCreate settings for that environment. You then run
grails dbm-create-changelog grails prod dbm-generate-gorm-changelog --add changelog-1.0.groovy
(Now would be a good time to try the new Grails 2 interactive mode!)
The above will create a grails-app/migrations/changelog.groovy file that will become your parent changelog file. The second command then generates a migration script, grails-app/migrations/changelog-1.0.groovy, that will take an empty database and create the appropriate schema for the current version of the application. The parent changelog is also updated to include this new one. Note that your database will remain empty!
Although Liquibase aims to be database agnostic, it's best to run the various generation and diff commands in a Grails environment that has a data source of the appropriate type configured, typically "production". This will ensure you don't have to make too many changes to the generated changelogs.
The plugin doesn't force any particular naming convention on you for the changelog filenames. In this article, I'm simply using 'changelog-
Why did we create a 1.0 changelog rather than use the "update" dbCreate setting? The initial changelog means that you can take your application and deploy it to a fresh, empty database. All the migrations will work as you'd expect, since they will run in order from a known, fixed schema (i.e. an empty one). The trouble with the 'update' setting is that it will aways create a schema that matches the current domain model. Older changelogs simply won't run because the database isn't in a state that they expect! You can still use "update" for your first application version if you want, but the above approach means you get an early chance to test your initial changelog.
At this point, it's work reviewing the changelog-1.0.groovy file to see what a migration looks like and whether anything needs changing. You might see more indexes and constraints added than you really need, so some trimming might be in order.
OK, so we now have a migration script now. But when we run grails prod run-app the application won't start up: the database is still empty. Why? The migration scripts do not automatically run on startup, which means that you either have to manually update the database using a command like dbm-update or enable migration execution on startup. I prefer the latter, particularly as there are many situations in which you simply don't have access to the production database from the Grails command line. So, add these settings to the Config.groovy file:
grails.plugin.databasemigration.updateOnStart = true grails.plugin.databasemigration.updateOnStartFileNames = ["changelog.groovy"]
Now when you start the application, the migration scripts will run and your application will work! And when you restart the server, the plugin will detect that the migrations have already run so they're ignored.
Subsequent changes
The whole point of database migrations is that you make changes to your database as your application evolves and control that process. So now imagine you've done some work on your application and want to release a new version, perhaps 1.0.1. To upgrade the production database you will need to create a changelog for your domain model changes. You could do this manually, but you can save a fair bit of effort by making using of the Database Migration plugin's 'diff' commands.
To perform a diff, you need a database that the plugin can compare the current domain model to. Again, it's a good idea to use the same type of database as you're using in production. Also, the database must be in its original state, i.e. before the current set of domain model changes were made. In other words, don't run your application against the database with dbCreate = "update" enabled! In fact, it's worth creating a database dump for every version of the application so that you can roll back in case you do accidentally update the database in this way.
OK, enough of the warnings. Let's create the next changelog:
grails prod dbm-gorm-diff --add changelog-1.0.1.groovy
As before, this will create a changelog-1.0.1.groovy file in the migrations directory and include it from the parent changelog. You will also need to check the generated changelog and potentially tweak it, but it's much easier to edit an existing file than create a new one from scratch. That's it! Now you can commit this change log to version control along with the corresponding domain class changes. In fact, I recommend that you always include domain class changes ant the corresponding migration script modifications in the same commit.
The changelog files themselves look like this:
databaseChangeLog = {
changeSet(id: "UpdateDescriminatorForPluginTabs", author: "pledbrook") {
update(tableName: "content") {
column name: "class", value: "org.grails.plugin.PluginTab"
where "title like 'description-%' and class = 'org.grails.wiki.WikiPage'"
}
}
changeSet(id: "IssuesUrlForPlugins", author: "pledbrook") {
addColumn(tableName: "plugin") {
column name: "issues_url", type: "varchar(255)", {
constraints nullable: true
}
}
}
}
As you can see, they are simply collections of change sets, where each change set contains some database refactorings. Each change set needs a unique ID per author per changelog so that Liquibase can track whether it has been applied or not (Liquibase tracks the change sets, not the changelogs!). The above example demonstrates how you can update existing data (id "UpdateDescriminatorForPluginTabs") as well as add a new column. Other supported refactorings include:
- Add/rename/modify column (schema change)
- Add index
- Add/rename/modify table
- Add/remove unique constraint
The full range of refactorings are described in the Liquibase manual, although all the examples are in XML. Fortunately, the mapping of XML -> Groovy is pretty straightforward:
- XML element -> Groovy method
- Attribute -> named argument
- Nested elements -> nested methods inside a closure
Finally, you may be wondering how to structure your changelogs. Should you have a change set per refactoring? Or a single change set per changelog? Or something in between? This is up to you, but a change set per source control commit can work well. In other words, you create a change set for each commit that contains changes to the domain model. Alternatively, you may want a change set per database table. Do whatever works for you.
That's all for this article. As you have seen, proper database migration support is an important part of any production database-backed web application, so it's great news that we now have an officially supported and very capable plugin to meet this need. It only works with relational databases (so no migrations for Redis, MongoDB, etc. I'm afraid) but it should still satisfy the requirements of the vast majority of Grails users. Go ahead and give it a try!
Similar Posts
- Countdown to Grails 2.0: Persistence
- Web Development Evolved: Grails 2.0 Released!
- Grails 1.3 Released
- Using Micro Cloud Foundry from Grails
- Reuse your Hibernate/JPA domain model with Grails





Glenn says:
Added on August 17th, 2011 at 11:58 amPeter,
Just wondering if this will work against an in memory database? I've been following your tutorial and in my bootstrap I am creating users and roles but it blows up every time saying that the table isn't found. I can see that the table in the changelog and I can see that the changelog is being run at start up. Any thoughts? Have you tried it against an in memory db?
Cheers,
Glenn
Peter Ledbrook (blog author) says:
Added on August 18th, 2011 at 5:18 amJust tried this out. Seems to work fine. I removed the 'dbCreate' setting from my development environment, enabled migrations on startup, and started my app. This could be an issue with the database migrations running in parallel to the bootstrap. Does it work if you don't try to create the domain instances in BootStrap?
Sean Gilligan says:
Added on August 19th, 2011 at 3:40 pm#!/bin/sh
APPVERSION=`awk -F'=' '/app.version/ { print $2 }' application.properties | tr -d '\r\n'`
echo "Creating migration for version ${APPVERSION}…"
grails prod dbm-gorm-diff –add changelog-${APPVERSION}.groovy
Sean Gilligan says:
Added on August 19th, 2011 at 5:12 pmWhy do you say "You will also need to check the generated changelog and potentially tweak it"? Is it because dbm-gorm-diff generates redundant changesets for addForeignKeyConstraint?
Other than this one issue, the plugin looks awesome. Should I create a JIRA issue?
Peter Ledbrook (blog author) says:
Added on August 22nd, 2011 at 9:48 amNice script Sean. Would probably be worth looking into a custom Grails script, because you could run that from interactive mode.
'dbm-gorm-diff' will sometimes generate redundant changesets, and as I understand it those changesets will be regenerated each time you run the command if the database doesn't already include them. I don't know what the exact details are regarding this topic, so I can't say much more. I'll see if I can get Burt, the author of the plugin, to provide some more definitive information here.
Note that you can maintain a second temporary database that is created via `dbCreate = "create"` and then diff that against the database that requires updating using the 'dbm-diff' command.
Predrag Knezevic says:
Added on September 2nd, 2011 at 4:21 pmWhat about using dbm-diff instead? According to my experience, it is more exact – I had situations where dbm-diff discovered that some indices should be placed in chageset (btw, they were really declared in -domain class mapping closures), and dbm-gorm-diff was completely silent about that.
Peter Ledbrook (blog author) says:
Added on September 12th, 2011 at 4:57 am@Predrag You can use dbm-diff as well. I compared the two with a fresh domain model, and dbm-gorm-diff worked identically to dbm-diff, but that's not to say it will always work the same. For complex models I imagine there will be differences. I think if you run into issues with dbm-gorm-diff, then it's worth switching to dbm-diff, but I don't think it's worth automatically using the latter unless the workflow suits you better, for example if you're already maintaining a second database that can work for migrations.
Certainly dbm-gorm-diff should be aware of indices defined in GORM mapping sections, so if that's not happening I would raise an issue.
Seth Fuller says:
Added on September 22nd, 2011 at 3:44 pmI tried out the plugin and ran into an issue when I tried to add a foreign key to a MySql database. An exception gets thrown and the SQLException says can't create table (schema_name.#sql-aa_9b). This is the exception:
2011-09-22 16:21:49,361 [main] ERROR liquibase – Error executing SQL ALTER TABLE `submission` ADD CONSTRAINT `FK84363B4C296C373F` FOREIGN KEY (`candidate_id`) REFERENCES `candidate` (`id`)
java.sql.SQLException: Can't create table 'schema_name.#sql-aa_9b' (errno: 150)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1073)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3597)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3529)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1990)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2151)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2619)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2569)
at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:813)
at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:656)
at liquibase.executor.jvm.JdbcExecutor$1ExecuteStatementCallback.doInStatement(JdbcExecutor.java:92)
I just pasted in the top portion of the stack trace until it got to the liquibase class.
This is the statement that caused the error:
changeSet(author: "sethfuller (generated)", id: "1316722236029-58") {
addForeignKeyConstraint(baseColumnNames: "candidate_id", baseTableName: "submission", constraintName: "FK84363B4C296C373F", deferrable: "false", initiallyDeferred: "false", referencedColumnNames: "id", referencedTableName: "candidate", referencesUniqueColumn: "false")
}
Peter Ledbrook (blog author) says:
Added on September 28th, 2011 at 9:22 am@Seth Did you raise that on the plugins forum?
Seth Fuller says:
Added on September 28th, 2011 at 9:30 amI later found that the problem was due to the fact that the candidate_id field was defined as bigint, while the candidate.id field was defined as "bigint unsigned". It is not really an error with the Database Migration plugin.
mchiareli says:
Added on November 7th, 2011 at 8:20 amThere is a way to generate a sql script from a specified changelog, without trying against a database?
I mean i want to generate a sql for my 0.2-changelog.groovy file, but i do not want to validate the database first, just want to sql output for this changelog, ignoring the current state of the database.
Peter Ledbrook (blog author) says:
Added on November 24th, 2011 at 11:26 am@mchiareli: I don't think so. All the SQL commands seem to require access to the database. I recommend you raise a feature request: http://jira.grails.org/browse/GPDATABASEMIGRATION
cblin says:
Added on December 16th, 2011 at 7:02 amPlease do not say : "Alternatively, you may want a change set per database table"
This is really bad !
From the moment you have 2 dependent tables and you rebuild a db from scratch, it is going to be a nightmare :
during development you execute file1.changeset1, file2.changeset1 and then you write and execute file1.changeset2.
but during deployment you execute file1.changeset1, file1.changeset2, and suddenly it fails because file1.changeset2 needed file2.changeset1…
Moreover, doing this hide the database evolution
You should evangelize the numbering practice instead : write a changelog for each commit and named it
001.changelog
002.changelog
this way, build from scratch is never a problem and you perfectly see how your database evolved
the only drawback is that 2 developpers may write the same file and this produce a conflict in the SCM.
you can mitigate the problems if you have 1 directory per related set of tables (i.e 1 directory per "module" of your application)
Hope that helps,
Seth Fuller says:
Added on December 16th, 2011 at 9:05 amRails uses a timestamp in its migration file names now for the same reason of two developers possibly creating a 002.changelog file. This would be a better solution. A new dbm-* target could be created to initialize a migration file with a timestamp in its name.
Nirav Assar says:
Added on December 22nd, 2011 at 5:13 pmThis is very nice and convenient. I had written a blog a few months ago on Burt's plugin. Much of it still applies. it has a practical example embedded.
http://assarconsulting.blogspot.com/2011/08/database-migration-in-grails.html
Peter Ledbrook (blog author) says:
Added on January 3rd, 2012 at 8:54 am@cblin: that's not what I'm suggesting, although I admit it's not clear. It seems to me you're talking about change logs rather than change sets. I would not advocate a change log (file) per table. However, within a change log, one could create change sets per table without problems since the whole change log is run.
In fact there are two conventions to devise: change log creation and how change sets are organised within each change log. You appear to be recommending a single change log per commit, which is one approach. I'm using the approach of a single change log per application version. The only thing I would recommend against is a single monolithic change log.
How you arrange change sets within those change logs is less critical in my view. And note that each changeset is identified by both its 'id' and the author.
Dmitry says:
Added on May 22nd, 2012 at 3:53 pmWhat would be the sequence of steps to generate SQL script of changes between two databases?
I don't want to create changelog table in my production system and want to run the DDl script manually before eeach time I deploy changes. The closest thus far id dbm-diff – but that creates a change set.
Peter Ledbrook (blog author) says:
Added on May 28th, 2012 at 9:00 am@Dmitry: first you need to create the changelog with dbm-diff. Then run dbm-update-sql against the production database to see what SQL will actually be executed.
typoking says:
Added on July 10th, 2012 at 4:23 pmHi Peter, I am confused by some of your instructions. The first command you write about (`grails dbm-create-changelog`) runs against development environment settings. When I run the this command should I have the `dbCreate` setting configured for my development environment? After running that command you write about a second command (`grails prod dbm-generate-gorm-changelog –add changelog-1.0.groovy`). You say the second command should be run with any `dbCreate` setting removed from production environment settings. At this point my production database is completely empty (no tables) so when I run the second command I get errors saying the tables cannot be found, though if I drop the `prod` from the command and run it against development settings I can generate a changelog.
More complications come when I let my production database be initialized from the migration scripts. After the migration script has run on the production database for the first time I immediately run `grails prod dbm-gorm-diff`. I expected to see no changes, but there are some changes, even though I have not made any changes to my domain classes. Further investigation shows that the `databasechangelog` table in my production database has only 65 entries where the same table in my development database has 102 entries. The production database was initialized from the development database changelog, so I don't understand why these differences exist.
Any guidance would be appreciated.
Thanks!
typoking says:
Added on July 10th, 2012 at 10:07 pmP.S. I have posted a question on Stackoverflow that contains specific details regarding this issue.
http://stackoverflow.com/questions/11424782/grails-database-migration-changelog-woes
Michael says:
Added on August 29th, 2012 at 11:46 amOf course the migrations plugin is only useful if your organization gives alter privileges to anyone other than DBAs. In my company only DBA's can alter DB schemas after going through a change approval process.
Peter Ledbrook (blog author) says:
Added on September 10th, 2012 at 8:09 am@typoking I have answered on StackOverflow, but your message above has some extra info. If the production database has fewer records than the development one and both dev and prod have the same changesets, then the migration probably failed after the 65th changeset. I recommend looking at stacktrace.log to see if there are any exceptions in there.
@Michael: That's not entirely true. You can still use the database migration plugin to generate an initial SQL changelog for the DBAs, and you can factor any of their changes back into the changesets. That would make it easier to mimic production in development, would it not?
Gervásio says:
Added on April 19th, 2013 at 12:51 pmI installed the plugin, but when I ran the application, the following error happend:
2013-04-19 16:39:58,168 [main] ERROR context.GrailsContextLoader – Error execut
at _GrailsRun_groovy$_run_closure5_closure12.doCall(_GrailsRun_groovy)
ing bootstraps: java.lang.ExceptionInInitializerError
org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.ExceptionInIni
tializerError
at org.grails.tomcat.TomcatServer.start(TomcatServer.groovy:212)
at grails.web.container.EmbeddableServer$start.call(Unknown Source)
at _GrailsRun_groovy$_run_closure5_closure12.doCall(_GrailsRun_groovy:15
at _GrailsSettings_groovy$_run_closure10.doCall(_GrailsSettings_groovy:2
80)
at _GrailsSettings_groovy$_run_closure10.call(_GrailsSettings_groovy)
at _GrailsRun_groovy$_run_closure5.doCall(_GrailsRun_groovy:149)
at _GrailsRun_groovy$_run_closure5.call(_GrailsRun_groovy)
at _GrailsRun_groovy.runInline(_GrailsRun_groovy:116)
at _GrailsRun_groovy.this$4$runInline(_GrailsRun_groovy)
at _GrailsRun_groovy$_run_closure1.doCall(_GrailsRun_groovy:59)
at RunApp$_run_closure1.doCall(RunApp.groovy:33)
at gant.Gant$_dispatch_closure5.doCall(Gant.groovy:381)
at gant.Gant$_dispatch_closure7.doCall(Gant.groovy:415)
at gant.Gant$_dispatch_closure7.doCall(Gant.groovy)
at gant.Gant.withBuildListeners(Gant.groovy:427)
at gant.Gant.this$2$withBuildListeners(Gant.groovy)
at gant.Gant$this$2$withBuildListeners.callCurrent(Unknown Source)
at gant.Gant.dispatch(Gant.groovy:415)
at gant.Gant.this$2$dispatch(Gant.groovy)
at gant.Gant.invokeMethod(Gant.groovy)
at gant.Gant.executeTargets(Gant.groovy:590)
at gant.Gant.executeTargets(Gant.groovy:589)
Caused by: java.lang.ExceptionInInitializerError
at liquibase.parser.ChangeLogParserFactory.(ChangeLogParserFactory
.java:39)
at liquibase.parser.ChangeLogParserFactory.getInstance(ChangeLogParserFa
ctory.java:24)
at DatabaseMigrationGrailsPlugin.register(DatabaseMigrationGrailsPlugin.
groovy:89)
at DatabaseMigrationGrailsPlugin.this$2$register(DatabaseMigrationGrails
Plugin.groovy)
at DatabaseMigrationGrailsPlugin$_closure2.doCall(DatabaseMigrationGrail
sPlugin.groovy:80)
… 23 more
Caused by: java.lang.SecurityException: Invalid signature file digest for Manife
st main attributes
at java.util.jar.JarVerifier.processEntry(JarVerifier.java:234)
at java.util.jar.JarVerifier.update(JarVerifier.java:188)
at java.util.jar.JarFile.initializeVerifier(JarFile.java:321)
at java.util.jar.JarFile.getInputStream(JarFile.java:386)
at java.net.URL.openStream(URL.java:1009)
at liquibase.servicelocator.ServiceLocator.setResourceAccessor(ServiceLo
cator.java:75)
at liquibase.servicelocator.ServiceLocator.(ServiceLocator.java:39
)
at liquibase.servicelocator.ServiceLocator.(ServiceLocator.java:
27)
… 28 more
Application context shutting down…
Application context shutdown.
———————————————————-
Can somebody help me?????