A Practical Guide to Using Liquibase with Spring Boot

Reetesh Kumar
6 min readSep 10, 2023

--

Introduction

In the fast-paced world of software development, managing database schemas and version control is crucial for maintaining data integrity and ensuring smooth application updates. Liquibase, an open-source tool, offers a robust solution to tackle this challenge. In this blog post, we’ll delve into Liquibase’s features, concepts, and its role in simplifying database management.

Liquibase in a Nutshell

At its core, Liquibase is a versatile database schema migration and version control tool. It empowers software developers and database administrators to handle database changes systematically and efficiently. By representing database changes as code, Liquibase streamlines collaboration, maintains consistency across environments, and automates the process of applying changes. Let’s explore Liquibase’s key components and functionalities.

The key components of Liquibase are:

Changelog: A changelog is a file that contains the history of all changes made to a database. It is typically written in XML, but can also be written in JSON or YAML.

Changeset: A changeset is a unit of change that is applied to the database. Each changeset is a self-contained unit of work that can be applied independently.

DatabaseChangeLog table: The DatabaseChangeLog table is a special table that Liquibase uses to track the status of the changelog. It stores information about the changesets that have been applied, the ones that are pending, and the ones that have been rolled back.

DatabaseChangeLogLock table: The DatabaseChangeLogLock table is a lock table that Liquibase uses to prevent concurrent changes to the database.

Liquibase Core: The Liquibase Core is the core library that provides the basic functionality of Liquibase. It includes classes for reading and parsing changelogs, applying changesets, and tracking the status of the database.

Liquibase Plugins: Liquibase Plugins are extensions that provide additional functionality to Liquibase. They can be used to support different database platforms, add new features, or customize the behavior of Liquibase.

In addition to these key components, Liquibase also provides a command-line tool, a Java API, and a Maven plugin. The command-line tool can be used to apply changesets, rollback changes, and generate reports.

The Java API can be used to programmatically apply changesets and manage the changelog.

The Maven plugin can be used to integrate Liquibase with Maven builds.

The Significance of Liquibase in Database Management

Let’s see now why Liquibase is indispensable for maintaining efficient and error-free databases.

1. Database Version Control → Consider the chaos that would ensue if software development were carried out without version control. Untracked changes, introduced bugs, and the inability to monitor progress would lead to a nightmarish scenario. This analogy extends to database management, where Liquibase steps in as a version control system. It ensures meticulous tracking, documentation, and reversibility of every database schema and data modification.

These version control features yield several significant advantages:

Enhanced Collaboration: Liquibase enables multiple developers to work concurrently on the database, mitigating concerns about conflicting changes.

Historical Insight and Auditing: The tool provides a clear historical record of database changes, proving invaluable for debugging and auditing purposes.
Rollback Capability: In case a change introduces issues, Liquibase offers the ability to roll back to a previous state, minimizing downtime and data loss.

2. Uniformity Across Environments → Discrepancies between development, testing, and production environments are commonplace in software development. Inconsistencies in database schema or data can give rise to obscure bugs and unforeseen complications. Liquibase takes center stage in maintaining uniformity across these environments by automating the application of changes.

Reproducible Builds: Liquibase guarantees the consistent construction and migration of your application’s database in all environments, diminishing the risk of “it works on my machine” issues.

3. Database Refactoring and Progression → As software applications evolve, so do their databases. Tasks like adding new tables, altering existing ones, or changing column data types are routine but fraught with potential errors. Liquibase offers a structured and controlled approach to execute these database refactorings.

Gradual Changes: Liquibase advocates making changes incrementally, breaking down complex database updates into manageable steps. This reduces the risk of application disruption during updates.

4. Cross-Platform Compatibility → In today’s software landscape, applications operate on a spectrum of database management systems, such as PostgreSQL, MySQL, Oracle, and Microsoft SQL Server. Liquibase supports multiple database platforms, granting you the advantage of a uniform toolset for database management, regardless of the underlying technology.

5. Automation and Integration with CI/CD → By integrating Liquibase into your Continuous Integration/Continuous Deployment (CI/CD) pipeline, you can streamline development processes significantly. Liquibase automates database schema changes, ensuring consistent application across all development and deployment phases. This automation minimizes human errors and accelerates the release of new features.

Liquibase with Spring Boot

Here, I’ll provide you with a step-by-step example of how to set up Liquibase in a Spring Boot project and define database changes using Liquibase change sets.

Step 1: Create a Spring Boot Project

We can create a Spring Boot project using your preferred IDE or by using the Spring Initializer website (https://start.spring.io/). Make sure to include the necessary dependencies for Spring Boot and the database we intend to use (e.g., H2, MySQL, PostgreSQL, etc.).

Step 2: Add Liquibase Dependencies

In project’s pom.xml (if you're using Maven) or build.gradle (if you’re using Gradle), add the Liquibase dependencies. For example, if you're using Maven, add the following dependencies:

<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.3</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<groupId>com.example.demo</groupId>
<artifactId>spring-boot-liquibase</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-liquibase</name>
<description>Demo project for Spring Boot With Liquibase</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

Step 3: Configure Liquibase

Create a liquibase.properties file or add Liquibase configuration to your application.yml (or application.properties) to specify the database connection details. Here's an example of an H2 in-memory database:

spring:
liquibase:
contexts: local
datasource:
url: jdbc:h2:~/demo
username: sa
password: ''
driverClassName: org.h2.Driver
jpa:
spring.jpa.database-platform: org.hibernate.dialect.H2Dialect
logging:
level:
"liquibase": info

In this example, we’re specifying the database URL, driver, username, password, and the Liquibase change log file (db-changelog-master.yaml) location. Also, we have setspring.liquibase.contexts(which is a comma-separated list of runtime contexts to use) to local (since we are running the application locally).

Step 4: Create Liquibase Change Sets

Create a directory structure for your Liquibase change sets. For example:

src/main/resources/db/changelog

Inside the changelog directory, create a db-changelog-master.yaml file (or a similar name) that references your individual change sets. For example:

databaseChangeLog:
- include:
file: db/tables/1.0.0_create_test_user.yaml

Inside the tables directory, create individual Liquibase change set YAML files. Liquibase also supports the .sql, .xml, or .json changelog formats.

For example, 1.0.0-create-test-user.yaml:

databaseChangeLog:
- changeSet:
id: create-table-test-user
author: liquibase
preConditions:
- onFail: MARK_RAN
not:
tableExists:
tableName: test_user
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
nullable: false
primaryKey: true
primaryKeyName: USER_PK
name: id
type: BIGINT
- column:
constraints:
nullable: false
name: username
type: VARCHAR(250)
- column:
constraints:
nullable: false
name: first_name
type: VARCHAR(250)
- column:
name: last_name
type: VARCHAR(250)
- column:
constraints:
nullable: false
name: email
type: VARCHAR(255)
tableName: test_user

Create a respective entity class for the table mentioned above as shown below:

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "test_user")
public class TestUser {

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "username")
private String userName;

@Column(name = "first_name")
private String firstName;

@Column(name = "last_name")
private String lastName;

@Column(name = "email")
private String email;
}

Step 5: Run Your Spring Boot Application

When you run your Spring Boot application, Liquibase will automatically apply the database changes defined in your change sets. You should see logs indicating the changes being applied.

You can find the example code at: https://github.com/reetesh043/spring-boot-liquibase

Conclusion

Efficient and error-free database management is paramount in today’s rapid software development landscape. Liquibase serves as a robust solution for version control, uniformity across environments, database refactoring, cross-platform compatibility, and automation. Incorporating Liquibase into the development workflow mitigates risks, enhances collaboration among the development teams, and ensures seamless database evolution alongside your applications.

Happy Learning !!!

References:

--

--

Reetesh Kumar

Software developer. Writing about Java, Spring, Cloud and new technologies. LinkedIn: www.linkedin.com/in/reeteshkumar1