DEV Community

Cover image for Database Migrations : Liquibase for Spring Boot Projects
Aymane Harmaz
Aymane Harmaz

Posted on • Edited on

Database Migrations : Liquibase for Spring Boot Projects

There are 2 ways with which Liquibase can be used in the context of a Spring Boot Project to perform database migrations, the first one is during the development phase where each developer will want add his own modifications and the modifications added by his collegues to his local database, the second one is during the deployment phase where we will want to gather all the modifications and run them against a database used by a released version of the project

In this post we will cover how Liquibase can be integrated with a Spring Boot project to help use perfrom database migration both phases, and we will be using examples from the repository that you can check at : Github Repository

Fundamental Concepts of Liquibase

Changeset and Migration File :

A changeset is the smallest coutable unit of change that Liquibase can perfrom and register on a target database and it can contain one or more operations.

A migration file is a file responsible for hosting one or more changesets.

In Liquibase executing a changeset does not necessarly mean executing the migration file that contains that changeset, because it may contain other changesets that were not executed

Changelog :

A changelog is a file containing a list of ordered changesets, or references to migration files containing changesets

DATABASECHANGELOG Table :

This is the table Liquibase creates on the target database and uses to keep track of what changesets have already been applied

A changeset won't be executed a second time if it has already been executed and registered in the DATABASECHANGELOG table

Each change set is identified in Liquibase by 3 properties, id, author, and filepath. When Liquibase executes a changeset it calculates a checksum for its content and stores it inside the DATABASECHANGELOG table with the 3 properties in order to make sure that a changeset has not been changed over time

If Liquibase notices that a changeset has been modified after it has been applied using the checksum calculations it will throw an error or a warning

Common Configuration

The first thing to do is to add the migration scripts containing the changesets intended to be applied on the target database :

  • V1__creating_schema.sql
  • V2__add_category_column_to_books_table.sql
  • V3__adding_authors_table.sql

Then we need to add a changelog file referencing the migration scripts following a specific order

<?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/migrations/V1__creating_schema.sql" />
    <include file="db/migrations/V2__add_category_column_to_books_table.sql" />
    <include file="db/migrations/V3__adding_authors_table.sql" />
</databaseChangeLog>
Enter fullscreen mode Exit fullscreen mode

Configuring Liquibase to run migrations on application startup

Most of the time this behavior of running migrations on the application startup is used locally (when the spring boot application is executed with the local profile), this is why we should add information about the database and the location of the changelog file in the local properties file (application-local.properties in the example)

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/demo_liquibase
    username: postgres
    password: changemeinproduction
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: none
  liquibase:
    change-log: classpath:/db/migrations/changelog.xml
Enter fullscreen mode Exit fullscreen mode

The next step is to add the dependency of liquibase in the pom.xml file of the project (build.gradle file if you are using Gradle)

<dependency>
  <groupId>org.liquibase</groupId>
  <artifactId>liquibase-core</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

When starting the application, Spring Boot will notice the presence of the liquibase dependency on the runtime classpath and will trigger the autoconfiguration classes related to liquibase, and an automatic migration process is going to be started against the configured database, here is an example of logging that we should get when starting the app :

2024-05-26T13:04:49.188+01:00  INFO 16644 --- [           main] liquibase.database                       : Set default schema name to public
2024-05-26T13:04:49.372+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Creating database history table with name: public.databasechangelog
2024-05-26T13:04:49.418+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Reading from public.databasechangelog
2024-05-26T13:04:49.476+01:00  INFO 16644 --- [           main] liquibase.lockservice                    : Successfully acquired change log lock
2024-05-26T13:04:49.478+01:00  INFO 16644 --- [           main] liquibase.command                        : Using deploymentId: 6725089478
2024-05-26T13:04:49.480+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Reading from public.databasechangelog
Running Changeset: db/migrations/V1__creating_schema.sql::1::aymane
2024-05-26T13:04:49.507+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Custom SQL executed
2024-05-26T13:04:49.510+01:00  INFO 16644 --- [           main] liquibase.changelog                      : ChangeSet db/migrations/V1__creating_schema.sql::1::aymane ran successfully in 18ms
Running Changeset: db/migrations/V2__add_category_column_to_books_table.sql::1::aymane
2024-05-26T13:04:49.523+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Custom SQL executed
2024-05-26T13:04:49.525+01:00  INFO 16644 --- [           main] liquibase.changelog                      : ChangeSet db/migrations/V2__add_category_column_to_books_table.sql::1::aymane ran successfully in 5ms
Running Changeset: db/migrations/V3__adding_authors_table.sql::1::aymane
2024-05-26T13:04:49.540+01:00  INFO 16644 --- [           main] liquibase.changelog                      : Custom SQL executed
2024-05-26T13:04:49.542+01:00  INFO 16644 --- [           main] liquibase.changelog                      : ChangeSet db/migrations/V3__adding_authors_table.sql::1::aymane ran successfully in 12ms
2024-05-26T13:04:49.547+01:00  INFO 16644 --- [           main] liquibase.util                           : UPDATE SUMMARY
2024-05-26T13:04:49.547+01:00  INFO 16644 --- [           main] liquibase.util                           : Run:                          3
2024-05-26T13:04:49.547+01:00  INFO 16644 --- [           main] liquibase.util                           : Previously run:               0
2024-05-26T13:04:49.547+01:00  INFO 16644 --- [           main] liquibase.util                           : Filtered out:                 0
2024-05-26T13:04:49.548+01:00  INFO 16644 --- [           main] liquibase.util                           : -------------------------------
2024-05-26T13:04:49.548+01:00  INFO 16644 --- [           main] liquibase.util                           : Total change sets:            3
2024-05-26T13:04:49.548+01:00  INFO 16644 --- [           main] liquibase.util                           : Update summary generated
2024-05-26T13:04:49.549+01:00  INFO 16644 --- [           main] liquibase.command                        : Update command completed successfully.
Liquibase: Update has been successful. Rows affected: 3
2024-05-26T13:04:49.555+01:00  INFO 16644 --- [           main] liquibase.lockservice                    : Successfully released change log lock
2024-05-26T13:04:49.557+01:00  INFO 16644 --- [           main] liquibase.command                        : Command execution complete
Enter fullscreen mode Exit fullscreen mode

Configuring Liquibase to run migrations independently from running the application

This behavior is used at the deployment phase when we will want to grab all the migration scripts added since the last release and execute them against database deployed on dev, staging or production environment

For that There is a Maven plugin for liquibase that can be used, but before adding it we should add a configuration file liquibase.yml to contain information about the target database and the location of the changelog file

url: jdbc:postgresql://localhost:5432/demo_liquibase
username: postgres
password: changemeinproduction
driver: org.postgresql.Driver
changeLogFile: src/main/resources/db/migrations/changelog.xml
Enter fullscreen mode Exit fullscreen mode

Then we should add the plugin to the pom file in the build section :

<build>
        <plugins>
            <plugin>
                <groupId>org.liquibase</groupId>
                <artifactId>liquibase-maven-plugin</artifactId>
                <version>4.5.0</version>
                <configuration>
                    <propertyFile>
                        src/main/resources/liquibase.yml
                    </propertyFile>
                </configuration>
            </plugin>
        </plugins>
</build>
Enter fullscreen mode Exit fullscreen mode

An important thing is to remember to disable triggering the liquibase migration process on application startup when starting the application with a profile like integration or production (not local profile), here is an example of the application-prod.yml file :

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/demo_liquibase
    username: postgres
    password: changemeinproduction
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: none
  liquibase:
    enabled: false
Enter fullscreen mode Exit fullscreen mode

Finally we can use the following command to trigger the migration process :

./mvnw liquibase:update
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using Liquibase with Spring Boot offers a robust solution for managing database changes in a controlled and efficient manner. It enables developers to focus on delivering features without worrying about the complexities of database migrations, making it an essential tool for any Spring Boot-based project.

Top comments (1)

Collapse
 
dband-drm profile image
dband drm

Hi Aymane.

Great walkthrough — the comparison between auto-configuration and programmatic setup is something a lot of teams skip over, and it matters more than people realise. The programmatic approach in particular is underrated: it gives you real control over when migrations run, which is useful if you're dealing with multi-tenant setups or need to gate migrations behind a feature flag.

One thing worth flagging for anyone running this in a real deployment pipeline (rather than just on app startup): Liquibase's spring.liquibase.enabled=false pattern is a good safety valve, but it pushes the question of "so when do migrations run?" back onto the team. A lot of shops end up with migrations baked into app startup for convenience, then run into trouble when they scale horizontally and multiple instances try to acquire the changelog lock at the same time. Liquibase handles this reasonably well with its lock table, but it's worth being intentional about it early.

The other thing that comes up at the pipeline layer — especially if your stack includes more than one database (say, Postgres for application data and SQL Server for reporting) — is coordinating those migrations as a single release. Liquibase handles individual databases well, but the orchestration across them is usually left to shell scripts or CI glue that's hard to audit.

We ran into exactly this problem and ended up building a thin layer on top of Liquibase (and Flyway, depending on the project) to handle multi-database releases, retry logic, and deployment history tracking. Not a replacement for what you've described — the Spring integration you've shown here is still the right approach for app-managed migrations — but worth knowing about if you find yourself needing more at the pipeline level.

We're building in public at github.com/dband-drm/drm-cli | @dband_drm on X


Enter fullscreen mode Exit fullscreen mode