Runtime Schema Upgrade

Since 3.2.0, MyBatis Migrations supports runtime schema upgrade (a.k.a. in-app migration).

If you distributed your application in binary form (e.g. WAR or JAR) and want to make some changes to the database schema after the initial release, that's where the Runtime Schema Upgrade helps.

Overview

To use Runtime Schema Upgrade, you need to create Migration Scripts.
Migration scripts can be written as text files (.sql) or java classes that implements MigrationScript interface.

Then during the startup process of your application, perform 'Up' operation by executing UpOperation#operate() method.
Assuming that your java migration scripts are in 'mycompany.migration.script' package and you can obtain java.sql.DataSource instance, the below is the minimum code to perform 'Up' operation.

new UpOperation().operate(
  new DataSourceConnectionProvider(dataSource),
    new JavaMigrationLoader("mycompany.migration.script"), null, null);

As the first migration script should be the one that creates the 'changelog' table, the initial release of your application may contain two or more migration scripts.
To upgrade the schema in the next version, just add new migration scripts to the package.

Migration Script

By default, MyBatis Migration supports two types of Migration Script: simple SQL script file (*.sql) and java migration script.

You have already learned about the simple SQL script file (*.sql) that can be generated by the migrate new command in the former sections. As it is just a text file, it is easy to write or edit, but a little bit harder to use with Runtime Schema Upgrade because it needs to be accessible in the file system (i.e. JAR or WAR must be extracted on the runtime environment).

A java migration script is a java class that implements MigrationScript interface (see below). It must be compiled, of course, but it can be placed anywhere in the classpath, so it may be a better choice for Runtime Schema Upgrade in most cases.

public interface MigrationScript {
  BigDecimal getId();
  String getDescription();
  String getUpScript();
  String getDownScript();
}

getId() method should return a unique ID of the migration script. Newer script must return a larger number.
getDescription() method should return a short description of the script.
getUpScript() method should return the actual SQL statements upgrading the schema.
getDownScript() method should return SQL statements downgrading the schema, but it exists mainly for API consistency and would not be used in Runtime Schema Upgrade.

Here is a typical implementation of the first migration script that creates the 'changelog' table:

package mycompany.migration.script

import java.math.BigDecimal;
import org.apache.ibatis.migration.MigrationScript;

public class V001_CreateChangelog implements MigrationScript {
  public BigDecimal getId() {
    return BigDecimal.valueOf(1L);
  }

  public String getDescription() {
    return "Create changelog";
  }

  public String getUpScript() {
    return "CREATE TABLE changelog ("
      + "ID NUMERIC(20,0) NOT NULL,"
      + "APPLIED_AT VARCHAR(25) NOT NULL,"
      + "DESCRIPTION VARCHAR(255) NOT NULL); "

      + "ALTER TABLE changelog "
      + "ADD CONSTRAINT PK_changelog "
      + "PRIMARY KEY (id);";
  }

  public String getDownScript() {
    return "DROP TABLE changelog;";
  }
}

UpOperation#operate()

As shown in the Overview section, Runtime Schema Upgrade is executed by calling the operate() method of an UpOperation instance.
The method takes four parameters.

  • ConnectionProvider: Required. Explained in the later section.
  • MigrationLoader: Required. Explained in the later section.
  • DatabaseOperationOption: Optional. If null is passed, the default values are used.
  • PrintStream: Optional. The result of the Up operation will be output to this stream.

ConnectionProvider

ConnectionProvider interface has only one method getConnection() which returns java.sql.Connection used by 'Up' operation.

public interface ConnectionProvider {
  Connection getConnection() throws SQLException;
}

There are two built-in implementations: DataSourceConnectionProvider and JdbcConnectionProvider.

You have already seen the example usage of DataSourceConnectionProvider in the Overview section. The constructor takes an instance of java.sql.DataSource as its only argument.

JdbcConnectionProvider takes four constructor arguments: JDBC driver class, JDBC URL, username and password.
Using JdbcConnectionProvider, the example code in the Overview section can be rewritten as follows.

new UpOperation().operate(
  new JdbcConnectionProvider("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:mydb", "myname", "mypassword"),
    new JavaMigrationLoader("mycompany.migration.script"), null, null);

MigrationLoader

MigrationLoader abstracts where and how to load your migration scripts.
There are two built-in implementations: FileMigrationLoader and JavaMigrationLoader.

FileMigrationLoader is used to load simple SQL scripts (*.sql).
Its constructor takes three arguments:

FileMigrationLoader(File scriptsDir, String charset, Properties properties)
  • scriptsDir is the only required parameter and it indicates the directory containing the migration scripts. Note that the directory must exist in the file system of the runtime environment.
  • charset is the character set of the migration scripts. If null is passed, system's default charset is used.
  • properties is used for variable substitution when reading the migration scripts (e.g. ${changelog}).

JavaMigrationLoader loads java classes which implement MigrationScript interface.
There are two constructors defined for JavaMigrationLoader

JavaMigrationLoader(String... packageNames)

JavaMigrationLoader(ClassLoader classLoader, String... packageNames)
  • packageNames is the list of packages that contains migration scripts. Note that the migration scripts are ordered by their IDs.
  • classLoader is used to search the migration scripts and is optional.