Database Migrations : Liquibase for Spring Boot Projects

aharmaz

Aymane Harmaz

Posted on May 26, 2024

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.

💖 💪 🙅 🚩
aharmaz
Aymane Harmaz

Posted on May 26, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related