Versioning Your Databases with Liquibase

Database versioning with Liquibase
Database versioning with Liquibase

A while ago, I worked on a collaborative project where we didn’t have any database versioning tool in place. As the project matured, we faced a situation where functional requirements were revised, and major changes had to be made to our database schemas.

Unsurprisingly, this overhaul of the data model ended in disaster. Data and schemas across different environments were inconsistent, leading to a lot of unusable data. The impact on the project was severe.

Thankfully, there are tools that can help prevent this kind of problem. In this article, I’ll introduce you to one such tool: Liquibase.

What is Liquibase?

Liquibase is a database versioning tool that helps track, manage, and apply changes to database schemas. There are several reasons why you might want to use Liquibase. Its powerful version control allows you to track how your database evolves over time. You can not only move forward with schema updates but also roll back to previous versions using its rollback system. This is especially useful in collaborative projects, ensuring that all developers are working with identical database schemas at each stage.

In a way, Liquibase is to databases what Git is to code.

Liquibase was launched in 2006 and is now compatible with over 50 Database Management Systems (DBMS). It supports both relational databases (like Oracle, MySQL, and PostgreSQL), NoSQL databases (such as MongoDB and Cassandra), and cloud databases (like Amazon RDS, Google Cloud SQL, and Microsoft Azure SQL). This broad compatibility gives Liquibase great flexibility and makes it easy to migrate a database from one system to another (more on this in the “How It Works” section below).

I’ll now walk you through a small, purely educational example of how Liquibase works. My goal isn’t to replace the official technical documentation but to give you a glimpse of what Liquibase can do.

Integrating Liquibase

  1. First, I create a database on my local machine:
Bash
   mysql > CREATE DATABASE liquibase_sample_database;
  1. In my IDE, I create a new project and select Maven as the build system.
  2. I then edit my pom.xml file to add the Liquibase dependency and plugin:
XML
   <dependencies>  
      <dependency>  
         <groupId>org.liquibase</groupId>  
         <artifactId>liquibase-core</artifactId>  
         <version>4.19.0</version>  
      </dependency>  
   </dependencies>
  1. After that, I create a properties file (liquibase.properties) in my project directory with the following content:
Bash
   driver=com.mysql.cj.jdbc.Driver
   url=jdbc:mysql://localhost:3306/liquibase_sample_database  
   username=YOUR_USERNAME  
   password=YOUR_PASSWORD  
   changeLogFile=1.0.0/db-changelog.xml
  1. Finally, I run mvn install to set everything up.

How It Works

ChangeSets

Liquibase operates through “ChangeSets.” These are markup files (XML, YAML, JSON, etc.) that describe the changes you want to apply to a database. These changes could be anything from creating, modifying, or deleting tables, columns, constraints, or even data. Essentially, they encompass all the operations you’d typically perform in a database.

Here’s an example of a simple ChangeSet in XML:

XML
<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="1" author="bmatthai">  
      <preConditions onFail="MARK_RAN">  
         <not>  
            <tableExists tableName="car"/>  
         </not>  
      </preConditions>  
      <createTable tableName="car">  
         <column name="id" type="BIGINT" autoIncrement="true">  
            <constraints primaryKey="true" nullable="false"/>  
         </column>  
         <column name="model" type="VARCHAR(255)" defaultValue="none">  
            <constraints nullable="false"/>  
         </column>  
      </createTable>  
   </changeSet>  
</databaseChangeLog>

In this example, we define two fields (id and model) in the Car table, along with some constraints like primaryKey and nullable. A good practice is to separate ChangeSets for schema modifications, complex constraints, and data changes, so they’re easier to manage and troubleshoot.

Logging and History

Once your ChangeSets are ready, you can apply them by running:

Bash
mvn install -f pom.xml

or

Bash
liquibase update

After executing, Liquibase will create two additional tables:

  • DATABASECHANGELOG: Tracks schema changes, ensuring an accurate history.
  • DATABASECHANGELOGLOCK: Prevents concurrent changes and ensures database consistency.

The DATABASECHANGELOG table also stores an MD5 checksum for each ChangeSet, preserving the integrity of the applied changes.

Rollback

Liquibase offers the ability to roll back changes. You can define a rollback operation in each ChangeSet, like this:

XML
<rollback>  
    <dropTable tableName="car"/>  
</rollback>

If a ChangeSet performs multiple operations, each one should have a corresponding rollback step.

Labels and Contexts

Labels and contexts allow you to control which ChangeSets are applied during migrations. Labels define specific versions or environments:

XML
<changeSet id="1" author="bmatthai" labels="1.0">

You can then filter by labels when running migrations:

Bash
liquibase update --labels=1.0

Similarly, contexts let you apply changes based on environments (e.g., local, production):

Bash
liquibase update --contexts=local

Conclusion

Whether you’re starting a new project or managing an existing one, I highly recommend using a versioning tool like Liquibase to manage your database changes. It saves time and simplifies collaboration among team members.

Summary

  • Liquibase is a powerful tool for database versioning, supporting multiple DBMS.
  • It works with ChangeSets that describe the changes to be applied to the database.
  • Once applied, ChangeSets shouldn’t be modified; instead, create new ones to undo or update existing changes.
  • DATABASECHANGELOG and DATABASECHANGELOGLOCK tables ensure consistency.
  • Labels and contexts help filter changes based on version or environment.

By leveraging Liquibase, you’ll avoid the chaos of inconsistent databases and enjoy a much smoother project workflow.