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: applechangelog.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::wesomeIf 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