The Liquibase
migrator executes ChangeSet
sequentially according to the ChangeLog
file. The ChangeSet
on top will run first and the change on the bottom will run later, but sometimes the requirement is to run some ChangeSet
at the very beginning or at the very end of execution.
One solution is to move the ChangeSet
at the beginning or at the end of the ChangeLog
file which is very error-prone. Let's see the same with an example
Table Apple
has an auto-increment
primary key, so whichever ChangeSet
runs first will be assigned the primary key
in order.
changelog.mysql.xml
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="wesome" id="1692599548266-1">
<createTable catalogName="appledb" tableName="apple">
<column autoIncrement="true" name="apple_id" type="BIGINT">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="apple_name" type="VARCHAR(255)" />
<column defaultValueComputed="CURRENT_TIMESTAMP" name="current_date_time" type="datetime" />
</createTable>
</changeSet>
<changeSet author="wesome" id="1692599548266-3">
<insert catalogName="appledb" tableName="apple">
<column name="apple_id" />
<column name="apple_name" value="Fuji" />
<column name="current_date_time" valueDate="now()" />
</insert>
</changeSet>
<changeSet author="wesome" id="1692599548266-2">
<insert catalogName="appledb" tableName="apple">
<column name="apple_id" />
<column name="apple_name" value="Macintosh" />
<column name="current_date_time" valueDate="now()" />
</insert>
</changeSet>
</databaseChangeLog>
liquibase.properties (update changeLogFile file format with SQL, XML, YAML, JSON as per changelog.mysql.<format>)
changeLogFile=changelog.mysql.xml
liquibase.command.url:jdbc:mysql://localhost:3306/AppleDb?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
liquibase.command.username: root
liquibase.command.password: rootroot
query apple table
select * from appledb.apple;
ChangeSet
with apple_name=Fuji
was first in order of execution and, hence was inserted first in the table.
to conform to the same logic, for testing, alter the ChangeSet
position and try running the same changelog.mysql.xml
.
The above approach is not the best practice and is very error-prone, to deal with this requirement Liquibase
provides a runOrder
attribute which takes first or last as value and runs ChangeSet
accordingly.
runOrder is not supported in formatted SQL changelog.
changelog.mysql.xml
<code class="language-bash">
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-latest.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="wesome" id="1692599548266-1" runOrder="first">
<createTable catalogName="appledb" tableName="apple">
<column autoIncrement="true" name="apple_id" type="BIGINT">
<constraints nullable="false" primaryKey="true" />
</column>
<column name="apple_name" type="VARCHAR(255)" />
<column defaultValueComputed="CURRENT_TIMESTAMP" name="current_date_time" type="datetime" />
</createTable>
</changeSet>
<changeSet author="wesome" id="1692599548266-2" runOrder="last">
<insert catalogName="appledb" tableName="apple">
<column name="apple_id" />
<column name="apple_name" value="Fuji" />
<column name="current_date_time" valueDate="now()" />
</insert>
</changeSet>
<changeSet author="wesome" id="1692599548266-3">
<insert catalogName="appledb" tableName="apple">
<column name="apple_id" />
<column name="apple_name" value="Macintosh" />
<column name="current_date_time" valueDate="now()" />
</insert>
</changeSet>
<changeSet author="wesome" id="1692599548266-4" runOrder="first">
<insert catalogName="appledb" tableName="apple">
<column name="apple_id" />
<column name="apple_name" value="Gala" />
<column name="current_date_time" valueDate="now()" />
</insert>
</changeSet>
</databaseChangeLog>
changelog.mysql.yaml
<code class="language-bash">
databaseChangeLog:
- changeSet:
id: 1692604842621-1
author: wesome
runOrder: first
changes:
- createTable:
catalogName: appledb
columns:
- column:
autoIncrement: true
constraints:
nullable: false
primaryKey: true
name: apple_id
type: BIGINT
- column:
name: apple_name
type: VARCHAR(255)
- column:
defaultValueComputed: CURRENT_TIMESTAMP
name: current_date_time
type: datetime
tableName: apple
- changeSet:
id: 1692604842621-2
author: wesome
runOrder: last
changes:
- insert:
catalogName: appledb
columns:
- column:
name: apple_id
valueNumeric: 1
- column:
name: apple_name
value: Fuji
- column:
name: current_date_time
valueDate: now()
tableName: apple
- changeSet:
id: 1692604842621-3
author: wesome
changes:
- insert:
catalogName: appledb
columns:
- column:
name: apple_id
valueNumeric: 2
- column:
name: apple_name
value: Macintosh
- column:
name: current_date_time
valueDate: now()
tableName: apple
- changeSet:
id: 1692604842621-4
author: wesome
runOrder: first
changes:
- insert:
catalogName: appledb
columns:
- column:
name: apple_id
valueNumeric: 3
- column:
name: apple_name
value: Gala
- column:
name: current_date_time
valueDate: now()
tableName: apple
changelog.mysql.json
{
"databaseChangeLog": [
{
"changeSet": {
"id": "1692604857670-1",
"author": "wesome",
"runOrder": "first",
"changes": [
{
"createTable": {
"catalogName": "appledb",
"columns": [
{
"column": {
"autoIncrement": true,
"constraints": {
"nullable": false,
"primaryKey": true
},
"name": "apple_id",
"type": "BIGINT"
}
},
{
"column": {
"name": "apple_name",
"type": "VARCHAR(255)"
}
},
{
"column": {
"defaultValueComputed": "CURRENT_TIMESTAMP",
"name": "current_date_time",
"type": "datetime"
}
}
],
"tableName": "apple"
}
}
]
}
},
{
"changeSet": {
"id": "1692604857670-2",
"author": "wesome",
"runOrder": "last",
"changes": [
{
"insert": {
"catalogName": "appledb",
"columns": [
{
"column": {
"name": "apple_id"
}
},
{
"column": {
"name": "apple_name",
"value": "Fuji"
}
},
{
"column": {
"name": "current_date_time",
"valueDate": "now()"
}
}
],
"tableName": "apple"
}
}
]
}
},
{
"changeSet": {
"id": "1692604857670-3",
"author": "wesome",
"changes": [
{
"insert": {
"catalogName": "appledb",
"columns": [
{
"column": {
"name": "apple_id"
}
},
{
"column": {
"name": "apple_name",
"value": "Fuji"
}
},
{
"column": {
"name": "current_date_time",
"valueDate": "now()"
}
}
],
"tableName": "apple"
}
}
]
}
},
{
"changeSet": {
"id": "1692604857670-4",
"author": "wesome",
"runOrder": "first",
"changes": [
{
"insert": {
"catalogName": "appledb",
"columns": [
{
"column": {
"name": "apple_id"
}
},
{
"column": {
"name": "apple_name",
"value": "Gala"
}
},
{
"column": {
"name": "current_date_time",
"valueDate": "now()"
}
}
],
"tableName": "apple"
}
}
]
}
}
]
}
liquibase.properties (update changeLogFile file format with SQL, XML, YAML, JSON as per changelog.mysql.<format>)
changeLogFile=changelog.mysql.xml
liquibase.command.url:jdbc:mysql://localhost:3306/AppleDb?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true
liquibase.command.username: root
liquibase.command.password: rootroot
Liquibase prints the ChangeSet
execution order in the log as well.
Running Changeset: changelog.mysql.xml::1692599548266-1::wesome
Running Changeset: changelog.mysql.xml::1692599548266-4::wesome
Running Changeset: changelog.mysql.xml::1692599548266-3::wesome
Running Changeset: changelog.mysql.xml::1692599548266-2::wesome
If changelog has create table ChangeSet, then it always should have runOrder="first" other wise, Liquibase migrator will try to run insert ChangeSet and result into table not exist error