Liquibase Concepts

All database changes are specified in the Liquibase changelog file. A change is contained in a changeset and changesets are added to the changelog in the order they need to be deployed.

Simply put – a changelog contains an ordered list of changesets, and a changeset contains a change.

You and your team can specify database changes in one of four different changelog formats: SQL, XML, JSON, or YAML. And, you can even mix and match different types of changelogs, if desired.

After your changelog is created, running liquibase update deploys any undeployed changes to a target database. Don’t worry, Liquibase keeps track of what’s deployed and what’s not deployed in the DATABASECHANGELOG table. When you run the liquibase update command, it brings the target database up-to-date without any need to intervene. When all of the changes have been deployed, subsequent calls to liquibase update return successfully without doing anything (since there’s nothing to do).

The following best practices provide guidance on the primary Liquibase components and workflow.

Organize your Changelogs

1. Define the directory structure

The most common way to organize changelogs is by major release.

Make sure to store your changelogs in source control, preferably near your database access code

In this example, we will use com/example/db/changelog.

 com
    example
       db
          changelogs
             db.changelog-root.xml
             db.changelog-1.0.xml
             db.changelog-1.1.xml
             db.changelog-2.0.xml
          DatabasePool.java
          AbstractDAO.java 

2. Set up the root changelog and included changelog files

The db.changelog-root.xml will automatically include the changelogs for each release in the alpha-numeric order.

Note: The db.changelog-root.xml is the changelog name that you will pass to all Liquibase calls.

For the example above, the changelog would look like the following:

<?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"
   xmlns:pro="http://www.liquibase.org/xml/ns/pro"
   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
      http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd
      http://www.liquibase.org/xml/ns/pro 
      http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd">  

    <includeAll path="com/example/changelogs/"/>  
</databaseChangeLog> 

The included changelogs can be in any format (including formatted SQL). Here are examples of the different changelog formats for the db.changelog-1.0 file:

--liquibase formatted sql
        
--changeset nvoxland:1
create table person (  
    id int primary key,
    name varchar(255)  
);

 

<?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"
   xmlns:pro="http://www.liquibase.org/xml/ns/pro"
   xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
      http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd
      http://www.liquibase.org/xml/ns/pro 
      http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd">

   <changeSet  author="nvoxland"  id="1">
      <createTable tableName="person">
         <column  name="id"  type="INTEGER">
            <constraints  nullable="false"  primaryKey="true"  unique="true"/>
         </column>
         <column  name="name"  type="VARCHAR(255)" />
      </createTable>
   </changeSet>

</databaseChangeLog>

 

{
  "databaseChangeLog": [
    {
      "changeSet": {
        "id": "1",
        "author": "nvoxland",
        "changes": [
          {
            "createTable": {
              "tableName": "person",
              "columns": [
                {
                  "column": {
                    "name": "id",
                    "type": "int",
                    "autoIncrement": true,
                    "constraints": {
                      "primaryKey": true,
                      "nullable": false
                    },
                  }
                },
                {
                  "column": {
                    "name": "name",
                    "type": "varchar(255)"
                  }
                }
              ]
            }
          }
        ]
      }
    }
  ]
}

 

databaseChangeLog:  
  -  preConditions:  
    -  runningAs:  
        username:  liquibase  

  -  changeSet:  
      id:  1  
      author:  nvoxland  
      changes:  
        -  createTable:  
            tableName:  person  
            columns:  
              -  column:  
                  name:  id  
                  type:  int  
                  autoIncrement:  true  
                  constraints:  
                    primaryKey:  true  
                    nullable:  false  
              -  column:  
                  name:  name  
                  type:  varchar(255)

 

Establish team changelog standards

1. One change per changeset

Avoid multiple changes per changeset so you can avoid failed auto-commit statements that can leave the database in an unexpected state.

2. Define the team’s changeset ID format

Choose a changeset ID that works for you. While it can be any string, we advise using an increasing number sequence starting at 1. Remember that each changeset ID needs to be unique within the changelog.

3. Document unclear or complicated changesets

Most of the time, changesets are self-documenting.

However, remember to use <comments> for any changesets where you need to explain non-obvious or complicated database changes to other developers. As they say, “A stitch in time saves nine!”

4. Have a rollback plan

Write changesets so they work with Liquibase rollback.

5. Manage your reference data

Leverage Liquibase to manage your reference data. Environment separation (DEV, QA, PROD) can be achieved using Liquibase contexts.

  • This is useful if you have test data that only gets deployed to QA environments.
  • It is also useful if you have data-fixes specific to the pre-production and production environments.

Manage stored logic

Typically, changesets in Liquibase remain untouched after they are deployed. Subsequent database changes are made when new ‘roll forward’ changesets are added to the end of the changelog.

However, stored logic (Stored Procedures, Functions, Packages, Triggers, etc.) acts more like application code than database schema changes. They are better managed similar to source code where you continually make updates to a single source file for each unit of stored logic. This also allows you to better see changes over time using standard git tools.

We recommend maintaining a separate changelog dedicated to stored logic. The combination of “create or replace” in your stored logic sql along with runOnChange="true" as an attribute on the changeset allows you to ensure changes get deploy when and only when a file changes. Specifically, setting this flag forces Liquibase to check if the changeset was modified. When it detects a modification, Liquibase deploys the (updated) change.

Develop software using this standard workflow

1. Using your favorite IDE or editor, create a new local changeset containing the change

2. Run liquibase update to execute the new changeset

3. Perform the corresponding changes in the application code

4. Test the new application code together with the database change

5. Commit both the changeset and the application code to source control

What’s next?

Use Liquibase to achieve CI/CD for databases

Database schema migrations are an essential task for every software project. Learn how to integrate Liquibase into your process to achieve CI/CD for databases.

Consider advanced Liquibase features and support

Advanced features and support designed for teams of all sizes are available via Liquibase.com.