Liquibase for Postgresql with Spring Boot
Databases are required in every application to store and index data. It becomes hassle if the database changes are not maintained well enough. As the application starts to grow database changes grow too. To maintain databases changes Liquibase is one tool to solve it.
NOTE: This post assumes that you have basic spring boot (v3.0) project setup with all the required dependencies and POSTGRESQl.
Liquibase Configuration for Spring boot
Add following dependency to pom.xml
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>4.23.2</version>
</dependency>
Configure liquibase properties in application.yml or application.properties file
spring:
liquibase:
change-log: classpath:changelog/main.xml
enabled: true
change-log tells the spring that all the database migrations are in the src/main/resources/changelog/main.xml file
enabled flag if true will run the liquibase configuration when the spring starts
main.xml will contain all the database migrations.
Database Migration
Create file with name ‘1690137730-init-schema.xml’ under src/main/resources/changelog folder and include it in main.xml.
You can name however you want, but it’s good to append timestamp to the file name so it easier to track the migrations.
<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-latest.xsd">
<include file="changelog/1690137730-init-schema.xml"/>
</databaseChangeLog>
Below are the details of 1690137730-init-schema.xml. Here we create two tables country and state and then add foreign key of country in the state table.
Few things to consider
- add unique changeSet id ( usually name of the file should suffice)
- add name of the author
- multiple changesets can be added into single file
<?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-latest.xsd">
<changeSet id="1690137730-init-sequences" author="my-restaurant" dbms="postgresql">
<createSequence sequenceName="country_country_id_seq" startValue="1" />
<createSequence sequenceName="state_state_id_seq" startValue="1" />
</changeSet>
<changeSet id="1690137730-init-schema" author="dev">
<createTable tableName="country">
<column name="country_id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar2(100)">
<constraints nullable="false"/>
</column>
<column name="updated" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP"/>
<column name="created" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
<createTable tableName="state">
<column name="state_id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar2(100)">
<constraints nullable="false"/>
</column>
<column name="code" type="varchar2(100)"/>
<column name="country_id" type="bigint">
<constraints nullable="false"/>
</column>
<column name="updated" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP"/>
<column name="created" type="timestamp" defaultValueComputed="CURRENT_TIMESTAMP"/>
</createTable>
<addForeignKeyConstraint baseTableName="state" baseColumnNames="country_id" constraintName="fK_state_country_id"
referencedTableName="country"
referencedColumnNames="country_id"/>
</changeSet>
</databaseChangeLog>
Apply Migrations
Assuming that you have valid postgresql connection string in the spring.datasource.url.
spring:
datasource:
url: jdbc:postgresql://localhost:5432/demodb?currentSchema=myapp
Run the application, this will run the migrations for the above datasource.url connection string. After the application runs successfully sequences and tables should be created under demodb/myapp schema.
Liquibase also creates two extra tables databasechangelog and databasechangeloglock to keep track which migrations have been applied, so that on next turn it won’t apply the migrations again which have been applied.
Entity Reference
Create State and Country entities. These entities will be used by the repositories for spring JPA
@Table(name = "state")
@Entity
@Getter
@Setter
public class State {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "stateSeq")
@SequenceGenerator(name = "stateSeq", sequenceName = "state_state_id_seq", allocationSize = 1)
@Column(name = "state_id")
private Long stateId;
@Column(name = "name", nullable = false)
@NotNull
private String name;
@Column(name = "code")
private String code;
@ManyToOne
@JoinColumn(name = "country_id")
@NotNull
private Country country;
@Column(name = "created", updatable = false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@CreatedDate
private LocalDateTime created;
@Column(name = "updated", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@LastModifiedDate
private LocalDateTime updated;
}
@Entity
@Table(name = "country")
@Getter
@Setter
public class Country {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "countrySeq")
@SequenceGenerator(name = "countrySeq", sequenceName = "country_country_id_seq", allocationSize = 1)
@Column(name = "country_id")
private Long countryId;
@Column(name = "name", nullable = false)
@NotNull
private String name;
@OneToMany(mappedBy = "country")
private List<State> states = new ArrayList<>();
@Column(name = "created", updatable = false, columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@CreatedDate
private LocalDateTime created;
@Column(name = "updated", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
@LastModifiedDate
private LocalDateTime updated;
}
Update Migrations
To add another migration, create new file under the changelog folder and include that in the main.xml. Add your changes with new changeset and run the application. On successful start of application new migrations should be applied too.
Conclusion
In this post we saw how keep track of the database migrations using the liquibase tool and integration with spring boot.