Migration Hooks

Since 3.3.0, MyBatis Migrations supports pre/post-migration hooks.

Overview

You can write scripts that are executed before or after up/down operation.
Hook scripts can be written in SQL or JSR-223 compliannt script languages.

Quick start

Here is what you need to do to use hook scripts.

  1. Create hooks directory.
  2. Create a hook script in hooks directory.
  3. Add a setting to the environment properties file.

1. Create hooks directory.

Create a directory named hooks in the base directory.

2. Create a hook script in hooks directory.

The following script outputs the famous string to the log.
Save this script in the hooks directory as hello.js.

print('Hello, World!');

3. Add a setting to the environment properties file.

To configure Migrations, add the following line to the development.properties in the environments directory.

hook_before_up=JavaScript:hello.js

The details will be explained in the later section, but the above line tells Migrations to execute hello.js at the beginning of migrate up operation.

Now, if you run migrate up, you will find the following lines in the log.

========== Applying JSR-223 hook : hello.js ==========
Hello, World!

NOTE The hook script will not be executed if there was no pending migration.

Configuration

As shown in the Quick Start section, migration hook is configured by adding a line in the environment properties file in key=value format.

Keys for available hooks

Here is the list of available hooks.

  • hook_before_up
  • hook_before_each_up
  • hook_after_each_up
  • hook_after_up
  • hook_before_down
  • hook_before_each_down
  • hook_after_each_down
  • hook_after_down
  • hook_before_new [1] (since 3.3.5)
  • hook_after_new [1] (since 3.3.5)
  • hook_before_script [1] (since 3.3.10)
  • hook_before_each_script [1] (since 3.3.10)
  • hook_after_each_script [1] (since 3.3.10)
  • hook_after_script [1] (since 3.3.10)

[1] Only JSR-223 script is supported.

Minimum setting : language and file name

The value part of the setting line consists of two or more segments separated with a colon :.
The first segment is the language name (e.g. SQL, JavaScript, Groovy, etc.). The second segment is the file name. These two segments are required. Here are some examples:

hook_before_up=SQL:insert_log.sql
hook_after_up=JavaScript:RestartServer.js

Constant variables

The other segments are used to define constant variables specific to this particular hook script. These variables can have arbitrary names, but there also are some special variable names for JSR-223 hooks that are explained in the later section.

The following settings reference the same hook script printvar.js with different variable values.

// development.properties
hook_before_up=JavaScript:printvar.js:when=before:what=up
hook_after_down=JavaScript:printvar.js:when=after:what=down

Constant variables can be referenced as global variables in JavaScript.

// printvar.js
print('This is ' + when + ' ' + what + ' hook.');

The above script will print This is before up hook. on migrate up and This is after down hook. on migrate down.

Also, if there are global variables defined in the environment properties file, they can be used in hook scripts in the same manner.

// development.properties
foo=bar

These variables can be used in SQL hook scripts as well.

# development.properties
hook_before_up=SQL:update_timestamp.sql:col=before
hook_after_up=SQL:update_timestamp.sql:col=after
// update_timestamp.sql
update worklog set ${col} = current_date();

Advanced usage of JSR-223 scripts

Get paths to the directories

An instance of SelectedPaths object is accessible as a global variable migrationPaths.

print(migrationPaths.getBasePath());
print(migrationPaths.getEnvPath());
print(migrationPaths.getScriptPath());
print(migrationPaths.getDriverPath());
print(migrationPaths.getHookPath());

About hookContext

A global variable hookContext is passed to each hook script, but the object bound to this variable depends on the hook.

Accessing Change object (in each hook only)

In an each hook script, an instance of Change object is accessible via the global variable hookContext.

print(hookContext.getChange().getId());
print(hookContext.getChange().getFilename());

NOTE The Change instance is a clone and will be discarded after each exection, so modifying it would be meaningless.

NOTE Change is not available to before_new and after_new hooks.

Execute SQL statement

You can execute arbitrary SQL statement via the built-in global object hookContext.

hookContext.executeSql("insert into worklog (str1) values ('done!');");

If you need more than just executing SQL statement, you can get an instance of java.sql.Connection from hookContext.

con = hookContext.getConnection();
try {
  stmt = con.createStatement();
  rs = stmt.executeQuery("select * from changelog");
  while (rs.next()) {
    print("id = " + rs.getString("id"));
  }
} finally {
  con.close();
  con = null;
  rs = null;
  stmt = null;
}

NOTE These methods are not available to before_new and after_new hooks.

Invoking function

When configuring JSR-223 hook scripts, it is possible to specify a top level function to invoke. The following script contains two functions foo and bar and bar takes two arguments.

// foobar.js
function foo() {
  print('foo');
}

function bar(id, name) {
  print(id + ':' + name);
}

To invoke these function, specify the function name using a special variable name _function and parameter values with _arg.

hook_before_up=js:foobar.js:_function=foo
hook_after_up=js:foobar.js:_function=bar:_arg=100:_arg=John

NOTE Some JSR-223 implementation may not support function invocation.

Invoking method

Similar to function invocation, it is also possible to invoke a method of an top level object.

// doggy.js
var dog = new Object();
dog.bark = function(who, times) {
  print('bow-wow ' + times + ' times at ' + who);
});

In the hook setting, use _object to specify the object name and _method to specify the method name.

hook_before_up=js:doggy.js:_object=dog:_method=bark:_arg=Lucy:_arg=128

NOTE Some JSR-223 implementation may not support method invocation.

Retain variable value throughout the operation

When single up/down operation executes multiple migrations, variables are reset on each migration.
There are two ways to retain variable value throughout the operation.

  1. Initialize the variable in before_up/down script and use it in before/after_each_up/down script.
    // before_up hook script
    var counter = 1;
    // before_each_up hook script
    print(counter++);
  2. Initialize only when the variable is undefined.
    if (typeof counter == 'undefined') this.counter = 1;
    println(counter++);

Use other languages than JavaScript

To use other JSR-223 compliant scripting language than JavaScript, you need to copy required .jar files to $MIGRATIONS_HOME/lib directory.

To write hook scripts in Groovy, for example, you will need groovy.jar and groovy-jsr223.jar.
Once the JARs are placed in $MIGRATIONS_HOME/lib directory, the rest is pretty much the same as JavaScript.
Save the following script as hello.groovy in hooks directory with the following content...

println('Hello groovy!')

...and add the setting to the environment properties file.

hook_before_up=groovy:hello.groovy