DEV Community

Anh Trần Tuấn
Anh Trần Tuấn

Posted on • Originally published at tuanh.net on

Stories about Liquibase and how to integrate it into Spring Boot applications

1. A short story

Dan is a single father, so he takes care of all the household chores. To ensure everything runs smoothly and he’s not late for work, Dan has set up an evening routine:

8 PM : Dinner.

9 PM : Prepare breakfast for the next day, which takes 1 hour.

10 PM : Go to bed to wake up on time the next morning.

6 AM next day : Wake up, make milk for his child, and cook breakfast.

8:30 AM next day : Arrive at the company on time.

This is Dan’s daily routine.

Image

However, something changed that day. Dan:

Image

8 PM : Dinner.

9 PM : Prepare breakfast for the next day, which takes 1 hour.

10 PM : Dan accidentally discovered an interesting game and decided to play until 2 AM.

2 AM next day : Dan went to bed.

7 AM next day : Dan woke up, hurriedly made milk for his child, and cooked breakfast.

9 AM next day : Dan arrived late for work and was scolded by his boss for being late.

The change in this plan caused Dan to have unexpected problems with his work.

Dan had his daily routine down to a science. It was a masterpiece. But then, late that night, he discovered a new game. It was a shock to his system, and everything came crashing down like a house of cards. The next morning, his boss wasn't happy to see him late. If only there had been some kind of alert to tell him to stop playing and go to bed, he could have kept his perfect schedule.

So, is it possible to create a schedule and get alerts when you're about to break it? Is this something that could actually work?

2. Related to database management

Dan has a clear daily plan, similar to how database management requires a detailed plan for the changes to be made.

Dan's distraction by games and deviation from the plan can be compared to not controlling database changes properly, leading to errors or inconsistencies in the system.

The warning system to remind Dan to go to bed on time can be compared to tools and methods in database management to monitor and control changes.

In database management, maintaining data integrity and system stability is very important. Similarly, Dan needs to adhere to his plan to maintain stability in his personal and work life.

When Dan does not follow the plan, the result is inconsistency (arriving late for work). In a database, inconsistency due to uncontrolled changes can lead to errors or incorrect data.

3. Related to Liquibase

Liquibase is a database management tool that facilitates the management of database schema changes. It leverages change files, often referred to as changelogs, to track and record modifications over time. By enforcing a structured approach, Liquibase ensures that database alterations are applied sequentially and consistently, minimizing the risk of errors.

This concept can be paralleled with Dan's daily routine. Just as Dan has a daily plan to maintain order and efficiency, Liquibase provides a structured framework for database management.

Both Dan and Liquibase follow a predefined plan. Any unplanned changes, whether it's Dan playing a game or an unexpected database modification, can disrupt the established order.

Adhering to the daily plan helps Dan maintain stability in his personal and professional life. Similarly, Liquibase ensures database integrity by tracking and controlling changes.

An alarm to remind Dan to sleep on time acts as a control mechanism. Likewise, Liquibase offers tools to monitor and control changes, preventing issues and ensuring data consistency.

Unexpected events like discovering a new game can disrupt Dan's routine. In the database context, Liquibase provides mechanisms to handle unforeseen changes or errors, allowing for recovery or adjustments.

Dan's daily plan allows him to track and adjust habits for improvement. Liquibase offers rollback capabilities, enabling users to revert to a previous database state if needed.

In essence, Liquibase provides a systematic approach to database management, much like a well-structured daily plan. By enforcing order, tracking changes, and providing mechanisms for control and recovery, Liquibase helps ensure the reliability and integrity of databases.

4. How to integrate it into Spring Boot applications

Add the following dependency to the pom.xml file.

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

After that, create a configuration file.

@Configuration
@EnableConfigurationProperties({ LiquibaseProperties.class})
public class LiquibaseConfiguration {

    private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class);

    private final Environment env;

    public LiquibaseConfiguration(Environment env) {
        this.env = env;
    }

    @Bean
    public SpringLiquibase liquibase(final DataSource dataSource) {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setChangeLog("classpath:databaseChangeLog.xml");
        liquibase.setDataSource(dataSource);
        return liquibase;
    }
}
Enter fullscreen mode Exit fullscreen mode

Afterwards, create a databaseChangeLog.xml file within the resources folder to hold the changesets. Each changeset represents a daily route of Dan, as discussed above.

<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext
   http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
   http://www.liquibase.org/xml/ns/dbchangelog
   http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">

    <include file="databaseChangeLogStandard.xml"></include>

    <changeSet author="blog" id="DEV-1">
        <createTable tableName="like">
            <column name="id" type="varchar(100)">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="article_id" type="varchar(255)"/>
            <column name="create_by" type="varchar(255)"/>
            <column name="create_at" type="TIMESTAMP" defaultValueDate="CURRENT_TIMESTAMP"/>
            <column name="modified_by" type="varchar(255)" defaultValue="0"/>
            <column name="modified_at" type="TIMESTAMP"/>
        </createTable>
    </changeSet>
    ......
</databaseChangeLog>
Enter fullscreen mode Exit fullscreen mode

You have two options for managing XML content: creating multiple small XML files and including them in a master file, or writing the content directly into the master file.

After that, you will receive login messages like these confirming successful

24-08-08 22:27:09 [main] INFO liquibase.lockservice - Successfully acquired change log lock
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Creating database history table with name: blog.DATABASECHANGELOG
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Reading from blog.DATABASECHANGELOG
2024-08-08 22:27:09 [main] INFO liquibase.lockservice - Successfully released change log lock
2024-08-08 22:27:09 [main] INFO liquibase.lockservice - Successfully acquired change log lock
Skipping auto-registration
2024-08-08 22:27:09 [main] WARN liquibase.hub - Skipping auto-registration
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table article created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Custom SQL executed
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table user_entity created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table comment created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table rate_tags created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table tokens created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - ChangeSet databaseChangeLogStandard.xml::init-database::blog ran successfully in 216ms
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table like created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - ChangeSet databaseChangeLog.xml::DEV-1::blog ran successfully in 13ms
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table pdf_file created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table book_subscriber created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Unique constraint added to book_subscriber(subscriber_id, file_id)
2024-08-08 22:27:09 [main] INFO liquibase.changelog - ChangeSet databaseChangeLog.xml::DEV-2::blog ran successfully in 56ms
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Table email_preference created
2024-08-08 22:27:09 [main] INFO liquibase.changelog - ChangeSet databaseChangeLog.xml::DEV-3::blog ran successfully in 23ms
2024-08-08 22:27:09 [main] INFO liquibase.changelog - Columns num_like(int) added to article
2024-08-08 22:27:09 [main] INFO liquibase.changelog - ChangeSet databaseChangeLog.xml::DEV-4::blog ran successfully in 146ms
2024-08-08 22:27:09 [main] INFO liquibase.lockservice - Successfully released change log lock
Enter fullscreen mode Exit fullscreen mode

When you try to change a pre-existing changset, you won't be able to start the application, and it will have the following error.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'tokenRepository' defined in com.blog.repositories.TokenRepository defined in @EnableJpaRepositories declared on DatabaseConfiguration: Cannot create inner bean '(inner bean)#32a4ecbe' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#32a4ecbe': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'liquibase' defined in class path resource [com/blog/configuration/LiquibaseConfiguration.class]: Invocation of init method failed; nested exception is liquibase.exception.ValidationFailedException: Validation Failed:
     1 change sets check sum
          databaseChangeLog.xml::DEV-1::blog was: 8:a65f5d884bb0759d939028af696a66c6 but is now: 8:73481352a034400de829cad6477b8306

    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:389)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:134)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1707)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1452)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
    ... 54 common frames omitted
Enter fullscreen mode Exit fullscreen mode

5. Liquibase table structure

When Liquibase is finished running, it creates two special tables called databasechangelog and databasechangeloglock, in addition to the tables you defined in your changeset.

Image

The DATABASECHANGELOG table in Liquibase plays a crucial role in tracking and managing database schema changes. Specifically, this table serves the following functions:

Recording Change History : The table stores information about applied database schema changes, including modifications such as creating tables, altering columns, adding indexes, and so on.

Tracking Status : This table helps Liquibase keep track of which changes have been applied, preventing the same change from being applied twice and ensuring consistency during deployment.

Version Management : The table records attributes like ID, AUTHOR, FILENAME, and DATEEXECUTED, aiding in version management and tracking the timing of changes.

Recovery and Rollback : When it's necessary to restore the database to a previous state, Liquibase can use the information in the DATABASECHANGELOG table to identify which changes need to be undone.

Image

The DATABASECHANGELOGLOCK table in Liquibase ensures that only one Liquibase process can modify the database at a time.

Database Change Lock : This table prevents concurrent database modifications by a Liquibase instance. It acts as a lock mechanism to ensure data integrity.

Integrity Protection : A Liquibase instance acquires an exclusive lock on the database before making changes, preventing other instances from interfering.

Lock Status Management : The table tracks the lock status, including the holder and timestamp.

Image

If the locker is set to 1, then

2024-08-08 22:46:06 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2024-08-08 22:46:06 [main] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2024-08-08 22:46:06 [main] INFO liquibase.lockservice - Waiting for changelog lock....
Enter fullscreen mode Exit fullscreen mode

Therefore, we can write mechanisms to set locker=1 when there is a problem with the database to disconnect it from the application.

5. Conclude

I hope this article has given you a good starting point for learning about Liquibase. If you have any questions, feel free to ask.

Thanks for reading, and I look forward to connecting with you again soon.

Top comments (0)