Streamlining Database Changes with Liquibase: A Spring Boot Integration Guide

Arvind Kumar
4 min readJan 1, 2025

--

Modern applications often require frequent database schema updates. Keeping these updates synchronized across environments (development, staging, production) can be challenging. This is where Liquibase, a database version control tool, shines.

In this article, we’ll explore how Liquibase helps manage database changes efficiently and demonstrate its power using a realistic industry example with an H2 database and Spring Boot.

What is Liquibase?

Liquibase is an open-source tool for database schema management. It allows developers to:

  • Track database changes in version control systems.
  • Apply updates consistently across environments.
  • Rollback changes when needed.
  • Automate database migrations as part of CI/CD pipelines.

The Problem It Solves

Without a tool like Liquibase, database updates can lead to:

  • Inconsistencies: Different environments having different schema versions.
  • Manual Errors: Developers making mistakes while applying SQL scripts manually.
  • Rollback Complexity: Difficulty reverting changes during failures.

Liquibase solves these issues by using declarative change logs (XML, YAML, JSON, or SQL) that describe schema changes in a controlled and auditable way.

Practical Industry Use Case

— — — — — — — — — — — — — — — — — — — — -

Problem Statement

A company needs to build a user management system with the following requirements:

  1. Initially, create a table to store user information.
  2. Later, add a column for phone_number to the users table.
  3. Add a roles table to manage user roles, along with a relationship table (user_roles).
  4. Demonstrate a rollback scenario where a change is reverted.

We’ll use Liquibase to manage these schema changes step by step.

Setting Up Liquibase with Spring Boot and H2

Step 1: Add Dependencies

Add the required dependencies in your build.gradle file:

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
implementation 'org.liquibase:liquibase-core'
}

Step 2: Configure the Application

Update the application.yml file:

spring:
datasource:
url: jdbc:h2:file:./data/testdb
username: sa
password:
driver-class-name: org.h2.Driver
liquibase:
change-log: classpath:db/changelog/db.changelog-master.xml
h2:
console:
enabled: true
path: /h2-console
  • Persistent Database: Using jdbc:h2:file:./data/testdb ensures that the database persists even after the server stops.
  • H2 Console: Access the H2 database console at http://localhost:8080/h2-console.

Step 3: First Iteration (Create Users Table)

Create a master changelog (db.changelog-master.xml) to include individual changesets:

<?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.8.xsd">
<include file="db/changelog/changeset-1-create-users.xml" />
</databaseChangeLog>

Define the first changeset to create the users table:

<?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.8.xsd">
<changeSet id="1" author="codefarm">
<createTable tableName="users">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true" />
</column>
<column name="username" type="varchar(50)">
<constraints nullable="false" unique="true" />
</column>
<column name="email" type="varchar(100)">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
</databaseChangeLog>

Restart the application to create the users table.

Verify: Access the H2 console and run:

SELECT * FROM users;

Step 4: Second Iteration (Add Phone Number)

Update the master changelog:

<include file="db/changelog/changeset-2-add-phone.xml" />

Define the second changeset to add the phone_number column:

<?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.8.xsd">
<changeSet id="2" author="codefarm">
<addColumn tableName="users">
<column name="phone_number" type="varchar(15)">
<constraints nullable="true" />
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

Restart the application to apply the change.

Verify: Run:

DESCRIBE users;

Step 5: Third Iteration (Add Roles Table)

Update the master changelog:

<include file="db/changelog/changeset-3-add-roles.xml" />

Define the third changeset to create the roles and user_roles tables:

<?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.8.xsd">
<changeSet id="3" author="codefarm">
<createTable tableName="roles">
<column name="id" type="int" autoIncrement="true">
<constraints primaryKey="true" />
</column>
<column name="role_name" type="varchar(50)">
<constraints nullable="false" unique="true" />
</column>
</createTable>
<createTable tableName="user_roles">
<column name="user_id" type="int">
<constraints nullable="false" />
</column>
<column name="role_id" type="int">
<constraints nullable="false" />
</column>
<addForeignKeyConstraint baseTableName="user_roles" baseColumnNames="user_id"
referencedTableName="users" referencedColumnNames="id" />
<addForeignKeyConstraint baseTableName="user_roles" baseColumnNames="role_id"
referencedTableName="roles" referencedColumnNames="id" />
</createTable>
</changeSet>
</databaseChangeLog>

Restart the application to apply the changes.

Verify: Run:

SELECT * FROM roles;
SELECT * FROM user_roles;

Step 6: Rollback Scenario

Imagine the phone_number column is no longer required. Liquibase allows you to rollback this change.

Rollback Configuration

Update the second changeset to include rollback instructions:

<?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.8.xsd">

<changeSet id="2" author="codefarm">
<addColumn tableName="users">
<column name="phone_number" type="varchar(15)">
<constraints nullable="true" />
</column>
</addColumn>
<rollback>
<dropColumn tableName="users" columnName="phone_number" />
</rollback>
</changeSet>
</databaseChangeLog>

Executing Rollback

Run the rollback command with Gradle:

./gradlew liquibaseRollback -PliquibaseCommandValue=1

Restart the application and verify:

DESCRIBE users;

The phone_number column will be removed.

Benefits of Liquibase

  1. Version Control: Tracks schema changes in a structured manner.
  2. Rollback Support: Reverts changes during failures (e.g., liquibase rollbackCount 1).
  3. Consistency Across Environments: Ensures all environments have the same schema.
  4. CI/CD Integration: Automates schema migrations in pipelines.

Conclusion

Liquibase is a powerful tool for managing database changes efficiently. By following this guide, you’ve learned how to:

  • Set up Liquibase with Spring Boot and H2.
  • Manage schema updates through multiple iterations.
  • Rollback changes seamlessly.
  • Maintain a consistent and version-controlled database schema.

If you found this useful clap the article and follow me for more article.

Here is the video on Youtube channel #codefarm to demo this which you should checkout —

https://www.youtube.com/watch?v=txaogG-LlR0

--

--

Arvind Kumar
Arvind Kumar

Written by Arvind Kumar

Staff Engineer @Chegg || Passionate about technology || https://youtube.com/@codefarm0

No responses yet