Spring Boot and Liquibase with TestContainers

Soy Phea
3 min readJun 20, 2020
  1. Overview

Liquibase is the database migration tool which help you to manage your , schema, table and data migration.

Come with the idea continuous deployment, automation is the key. Using tool like Liquibase is helping you to accelerate your software development and deploy your software faster. Because when you have the scripts which already create the tables, add columns, alter column, initialised data in your local or dev environment. The the rest of environment should be automated.

Liquibase changLog file is really awesome which support lots of format like JSON, XML, yaml it’s depend on which one is you prefer. Moreover, Liquibase support with various of RDBMS like PostgreSQL, MySQL, Oracle etc.

2. Spring Boot with Liquibase

Be able to use Liquibase in Spring Boot application, what you have to do is adding the Liquibase dependency:

<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>

By default Liquibase using the primary datasource. If you wish to use different datasource Liquibase. You can change following configuration:

spring:
liquibase:
enabled: true
user: liquibase_user
password: liquibase_password

Then you can go ahead to create a file db.changelog-master.yaml under /resources/db.changelog folder example:

databaseChangeLog:
- include:
file: db/changelog/db.changelog-1.0.yml
- include:
file: db/changelog/db.changelog-1.1.yml

By default Spring Boot auto configuration take a look on this class path and yaml format.However, it doesn’t prevent you to change the location and format you love. You can just change this property in application.properties or application.yaml.

spring:
liquibase:
enabled: true
change-log: classpath:/db-migrations/changelog-master.xml

I prefer yaml format ( — ).

Create table with changeSet

databaseChangeLog:
- changeSet:
id: "20200607-book"
author: phea_soy
preConditions:
- onFail: MARK_RAN
not:
tableExists:
tableName: book
changes:
- createTable:
tableName: book
columns:
- column:
autoIncrement: true
constraints:
primaryKey: true
primaryKeyName: pk_book
name: id
type: BIGSERIAL
- column:
name: title
type: VARCHAR(50)
constraints:
nullable: false
- column:
name: snb
type: VARCHAR(50)
constraints:
nullable: false
- column:
name: price
type: numeric(5,3)
constraints:
nullable: false

Insert data with ChangeSet

- changeSet:
id: 20200607-book-insert
author: phea_soy
changes:
- insert:
columns:
- column:
name: id
value: 2
- column:
name: title
value: Enterprise Kubernetes
- column:
name: snb
value: whatever
- column:
name: price
value: 10.000
tableName: book

3. Testing your Liquibase changeLog with TestContainers

  • Because I am using PostgreSQL, I would like to add the maven dependency for postgresql testcontainers.
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.14.3</version>
</dependency>
  • Setup your test class with PostgreSQLContainer
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>()
.withUsername("user")
.withPassword("password")
.withDatabaseName("test_db");

@DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
postgres.start();
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
registry.add("spring.liquibase.contexts", () -> "!prod");
}

When test class executed, the PostgreSQLContainer will start then in our postgresqlProperties method will inject the property for container to the PropertySource for testing.

@Test
@DisplayName("Test find book by id from liquibase initialised")
public void find_book_by_id(){
Optional<Book> bookOptional = bookRepository.findById(2);
Assertions.assertThat(bookOptional.isPresent()).isEqualTo(true);
}
  • Because we had add spring.liquibase.contexts = !prod, then we can test to make sure the changSet with context = prod should not execute.
@Test
@DisplayName("Test liquibase context prod should not execute ")
public void find_book_by_id_context_prod(){
Optional<Book> bookOptional = bookRepository.findById(10);
Assertions.assertThat(bookOptional.isPresent()).isEqualTo(false);
}

4. Liquibase Best Practices

  • Avoid changSet with multiple change
  • Separate the master changeLog with actual changeLog. Master should contain the sub changeLog.
  • Using different users for Liquibase and application datasource to limit some privilege for application. Example: application user should not have permission to drop table.
  • Avoid to use SQL native for specific RDBMS because it will be not ok, if we want to work with different current RDBMS

5. Conclusion

In this post, we have learnt Liquibase, TestContainers, and how can Liquibase integrate with Spring Boot. So if you would like to learn more with Liquibase, you should take a look on context, lable, rollback and async task with Liquibase. Here is my source code.

--

--