Micronaut Liquibase

Integration between Micronaut and Liquibase

Version: 6.3.0

1 Introduction

The Micronaut Liquibase integration runs Liquibase changelogs. It does not create changelogs.

To use the Micronaut’s integration with Liquibase you must have the micronaut-liquibase dependency on your classpath:

implementation("io.micronaut.liquibase:micronaut-liquibase")
<dependency>
    <groupId>io.micronaut.liquibase</groupId>
    <artifactId>micronaut-liquibase</artifactId>
</dependency>

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Breaking Changes

Micronaut Framework 4 uses Groovy 4 and it does not support GORM 7. Micronaut Liquibase will support GORM once there is a GORM version built with Groovy 4.

4 Configuration

You can define liquibase configuration for each datasource. The following example demonstrates using it:

datasources.default.url=jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE
datasources.default.username=sa
datasources.default.password=
datasources.default.driverClassName=org.h2.Driver
jpa.default.packages-to-scan[0]=example.micronaut
jpa.default.properties.hibernate.hbm2ddl.auto=none
jpa.default.properties.hibernate.show_sql=true
liquibase.datasources.default.change-log=classpath:db/liquibase-changelog.xml
datasources:
    default:
        url: 'jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE'
        username: 'sa'
        password: ''
        driverClassName: 'org.h2.Driver'
jpa:
    default:
        packages-to-scan:
            - 'example.micronaut'
        properties:
            hibernate:
                hbm2ddl:
                    auto: none
                show_sql: true
liquibase:
    datasources: # (2)
        default: # (3)
            change-log: 'classpath:db/liquibase-changelog.xml' # (4)
[datasources]
  [datasources.default]
    url="jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE"
    username="sa"
    password=""
    driverClassName="org.h2.Driver"
[jpa]
  [jpa.default]
    packages-to-scan=[
      "example.micronaut"
    ]
    [jpa.default.properties]
      [jpa.default.properties.hibernate]
        show_sql=true
        [jpa.default.properties.hibernate.hbm2ddl]
          auto="none"
[liquibase]
  [liquibase.datasources]
    [liquibase.datasources.default]
      change-log="classpath:db/liquibase-changelog.xml"
datasources {
  'default' {
    url = "jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE"
    username = "sa"
    password = ""
    driverClassName = "org.h2.Driver"
  }
}
jpa {
  'default' {
    packagesToScan = ["example.micronaut"]
    properties {
      hibernate {
        hbm2ddl {
          auto = "none"
        }
        show_sql = true
      }
    }
  }
}
liquibase {
  datasources {
    'default' {
      changeLog = "classpath:db/liquibase-changelog.xml"
    }
  }
}
{
  datasources {
    default {
      url = "jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE"
      username = "sa"
      password = ""
      driverClassName = "org.h2.Driver"
    }
  }
  jpa {
    default {
      packages-to-scan = ["example.micronaut"]
      properties {
        hibernate {
          hbm2ddl {
            auto = "none"
          }
          show_sql = true
        }
      }
    }
  }
  liquibase {
    datasources {
      default {
        change-log = "classpath:db/liquibase-changelog.xml"
      }
    }
  }
}
{
  "datasources": {
    "default": {
      "url": "jdbc:h2:mem:liquibaseDisabledDb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=TRUE",
      "username": "sa",
      "password": "",
      "driverClassName": "org.h2.Driver"
    }
  },
  "jpa": {
    "default": {
      "packages-to-scan": ["example.micronaut"],
      "properties": {
        "hibernate": {
          "hbm2ddl": {
            "auto": "none"
          },
          "show_sql": true
        }
      }
    }
  },
  "liquibase": {
    "datasources": {
      "default": {
        "change-log": "classpath:db/liquibase-changelog.xml"
      }
    }
  }
}
  • properties.hibernate.hbm2ddl.auto set to none disables schema DDL creation.

  • liquibase.datasources defines all liquibase configurations, for example defauilt in the example.]

  • The root changelog is src/main/resources/db/liquibase-changelog.xml.

Often, you will have a root changelog:

resources/db/liquibase-changelog.xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
  <include file="changelog/01-create-books-schema.xml" relativeToChangelogFile="true"/>
  <include file="changelog/02-insert-data-books.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>

which imports changelogs which you keep generating as your app evolves:

resources/db/changelog/01-create-books-schema.xml
<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
  <changeSet id="01" author="sdelamo">
    <createTable tableName="books"
      remarks="A table to contain all books">
      <column name="id" type="int">
        <constraints nullable="false" unique="true" primaryKey="true"/>
      </column>
      <column name="name" type="varchar(255)">
        <constraints nullable="false" unique="true"/>
      </column>
    </createTable>
  </changeSet>
</databaseChangeLog>
Liquibase migrations are executed when datasources are created. Because Micronaut beans are, by default, created lazily, if you do not inject a Datasource somewhere, then migrations are not executed. This may be the case when you create a command in a separate module just to run migrations, e.g. using Micronaut support for picocli. In this case it is enough to inject a Datasource in anyone of your singletons and migrations will be executed.

Run migrations manually

If you need more control to decide when the migrations are executed it is possible to configure the application like this:

liquibase.enabled=true
liquibase.datasources.default.enabled=false
liquibase:
  enabled: true
  datasources:
    default:
      enabled: false
[liquibase]
  enabled=true
  [liquibase.datasources]
    [liquibase.datasources.default]
      enabled=false
liquibase {
  enabled = true
  datasources {
    'default' {
      enabled = false
    }
  }
}
{
  liquibase {
    enabled = true
    datasources {
      default {
        enabled = false
      }
    }
  }
}
{
  "liquibase": {
    "enabled": true,
    "datasources": {
      "default": {
        "enabled": false
      }
    }
  }
}
  • Liquibase needs to be enabled

  • Liquibase migrations can be disabled for a specific datasource

Now you can inject the LiquibaseMigrator bean and call manually the method run to execute the migrations when you want.

There are several options available for configuration:

🔗
Table 1. Configuration Properties for LiquibaseConfigurationProperties
Property Type Description

liquibase.datasources.*.test-rollback-on-update

boolean

Returns whether a rollback should be tested at update time or not.

liquibase.datasources.*.database-change-log-lock-table

java.lang.String

Name of table to use for tracking concurrent Liquibase usage.

liquibase.datasources.*.database-change-log-table

java.lang.String

Name of table to use for tracking change history.

liquibase.datasources.*.liquibase-tablespace

java.lang.String

Tablespace to use for Liquibase objects.

liquibase.datasources.*.liquibase-schema

java.lang.String

Schema to use for Liquibase objects.

liquibase.datasources.*.change-log

java.lang.String

liquibase.datasources.*.rollback-file-path

java.lang.String

liquibase.datasources.*.drop-first

boolean

liquibase.datasources.*.default-schema

java.lang.String

liquibase.datasources.*.parameters

java.util.Map

liquibase.datasources.*.tag

java.lang.String

liquibase.datasources.*.contexts

java.lang.String

Comma-separated list of runtime contexts to use.

liquibase.datasources.*.labels

java.lang.String

Comma-separated list of runtime labels to use.

liquibase.datasources.*.enabled

boolean

Sets whether this liquibase configuration is enabled. Default value (true).

liquibase.datasources.*.async

boolean

Logging

Liquibase 4.0 has changed how logging is done and now it is based on Java Util Logging (JUL). This means that without doing anything the log format won’t be the usual in Micronaut:

16:39:58.744 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
16:39:58.945 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
Nov 10, 2020 4:39:59 PM liquibase.lockservice
INFO: Successfully acquired change log lock
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: Creating database history table with name: PUBLIC.DATABASECHANGELOG
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: Reading from PUBLIC.DATABASECHANGELOG
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: Table users created
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: ChangeSet db/changelog/01-create-users-table.xml::01::ilopmar ran successfully in 10ms
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: New row inserted into users
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: New row inserted into users
Nov 10, 2020 4:39:59 PM liquibase.changelog
INFO: ChangeSet db/changelog/02-insert-users-data.xml::02::ilopmar ran successfully in 4ms
Nov 10, 2020 4:39:59 PM liquibase.lockservice
INFO: Successfully released change log lock
16:40:00.277 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2392ms. Server Running: http://localhost:8080

If you want to integrate JUL and Logback you need to do the following in your application:

  • Add the dependency:

implementation("org.slf4j:jul-to-slf4j:1.7.30")
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.30</version>
</dependency>

  • Add the following contextListener to your logback.xml:

src/main/resources/logback.xml
<configuration>

    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>

    <appender ...>
        ...
    </appender>

    ...

</configuration>
  • Initialize the JUL to Slf4j bridge. You can add the following to your Application class:

src/main/java/my/package/Application.java
public static void main(String[] args) {
    // Bridge JUL to Slf4j
    SLF4JBridgeHandler.removeHandlersForRootLogger();
    SLF4JBridgeHandler.install();

    Micronaut.run(Application.class, args);
}

With the previous configuration, starting your application again will show the logs properly:

16:47:10.868 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
16:47:11.042 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
16:47:11.344 [main] INFO  liquibase.lockservice - Successfully acquired change log lock
16:47:11.744 [main] INFO  liquibase.changelog - Creating database history table with name: PUBLIC.DATABASECHANGELOG
16:47:11.747 [main] INFO  liquibase.changelog - Reading from PUBLIC.DATABASECHANGELOG
16:47:11.844 [main] INFO  liquibase.changelog - Table users created
16:47:11.844 [main] INFO  liquibase.changelog - ChangeSet db/changelog/01-create-users-table.xml::01::ilopmar ran successfully in 20ms
16:47:11.857 [main] INFO  liquibase.changelog - New row inserted into users
16:47:11.858 [main] INFO  liquibase.changelog - New row inserted into users
16:47:11.859 [main] INFO  liquibase.changelog - ChangeSet db/changelog/02-insert-users-data.xml::02::ilopmar ran successfully in 3ms
16:47:11.861 [main] INFO  liquibase.lockservice - Successfully released change log lock
16:47:12.288 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2213ms. Server Running: http://localhost:8080

5 GraalVM support

Micronaut Liquibase is compatible with GraalVM so it is possible to create native images and run the migrations during application startup.

Everything is handled automatically by the library so users don’t need any special configuration.

See the section on GraalVM in the user guide for more information.

6 Endpoint

This configuration provides a built-in endpoint to expose all the applied migrations in /liquibase.

To enable the endpoint add the following to the configuration:

endpoints.liquibase.enabled=true
endpoints.liquibase.sensitive=false
endpoints:
    liquibase:
        enabled: true
        sensitive: false
[endpoints]
  [endpoints.liquibase]
    enabled=true
    sensitive=false
endpoints {
  liquibase {
    enabled = true
    sensitive = false
  }
}
{
  endpoints {
    liquibase {
      enabled = true
      sensitive = false
    }
  }
}
{
  "endpoints": {
    "liquibase": {
      "enabled": true,
      "sensitive": false
    }
  }
}
  • /liquibase endpoint is enabled (this is the default), and open for unauthenticated access.

$ curl http://localhost:8080/liquibase

[{
    "name": "default",
    "changeSets": [{
        "author": "sdelamo",
        "changeLog": "classpath:db/changelog/01-create-books-and-author-schema.xml",
        "comments": "",
        "contexts": [],
        "dateExecuted": "2018-10-29T16:33:05Z",
        "deploymentId": "0830784929",
        "description": "createTable tableName=books; createTable tableName=authors; addForeignKeyConstraint baseTableName=books, constraintName=author_fk, referencedTableName=authors",
        "execType": "EXECUTED",
        "id": "01",
        "labels": [],
        "checksum": "8:140eb966bb6a14bccade2c2d9133b7d3",
        "orderExecuted": 1,
        "tag": "tag1"
    }, {
        "author": "sdelamo",
        "changeLog": "classpath:db/changelog/02-insert-data-authors.xml",
        "comments": "Inserting Authors",
        "contexts": [],
        "dateExecuted": "2018-10-29T16:33:05Z",
        "deploymentId": "0830784929",
        "description": "insert tableName=authors; insert tableName=authors; insert tableName=authors; insert tableName=authors; insert tableName=authors",
        "execType": "EXECUTED",
        "id": "02",
        "labels": [],
        "checksum": "8:6204c525ce5c1c55f064888d078b8f05",
        "orderExecuted": 2,
        "tag": null
    }]
}]
See the section on Built-in endpoints in the user guide for more information.

7 Guides

See the guide for Schema Migration With Liquibase to learn more.

8 Repository

You can find the source code of this project in this repository: