What is Liquibase?
Liquibase is a database schema change management tool that tracks, versions, and deploys database changes. It supports multiple databases and file formats including XML, YAML, JSON, and SQL.
Quick Setup
Add dependency to pom.xml:
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
Configure in application.properties:
spring.liquibase.enabled=true
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.yaml
spring.liquibase.contexts=dev,prod
Project Structure
src/main/resources/
db/
changelog/
db.changelog-master.yaml
changes/
001-create-users.yaml
002-add-email-column.yaml
Master Changelog
db.changelog-master.yaml:
databaseChangeLog:
- include:
file: db/changelog/changes/001-create-users.yaml
- include:
file: db/changelog/changes/002-add-email-column.yaml
Creating Tables
001-create-users.yaml:
databaseChangeLog:
- changeSet:
id: 001
author: developer
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: username
type: VARCHAR(50)
constraints:
nullable: false
unique: true
- column:
name: created_at
type: TIMESTAMP
defaultValueComputed: CURRENT_TIMESTAMP
Adding Columns
002-add-email-column.yaml:
databaseChangeLog:
- changeSet:
id: 002
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: email
type: VARCHAR(100)
constraints:
nullable: false
unique: true
Creating Foreign Keys
003-create-orders.yaml:
databaseChangeLog:
- changeSet:
id: 003
author: developer
changes:
- createTable:
tableName: orders
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: user_id
type: BIGINT
- column:
name: total
type: DECIMAL(10,2)
- addForeignKeyConstraint:
baseTableName: orders
baseColumnNames: user_id
referencedTableName: users
referencedColumnNames: id
constraintName: fk_orders_user
Rollback Support
Define rollback for each change:
databaseChangeLog:
- changeSet:
id: 004
author: developer
changes:
- addColumn:
tableName: users
columns:
- column:
name: phone
type: VARCHAR(20)
rollback:
- dropColumn:
tableName: users
columnName: phone
Using Contexts
Different changes for different environments:
databaseChangeLog:
- changeSet:
id: 005
author: developer
context: dev
changes:
- insert:
tableName: users
columns:
- column:
name: username
value: test_user
- column:
name: email
value: test@example.com
Preconditions
Add safety checks before execution:
databaseChangeLog:
- changeSet:
id: 006
author: developer
preConditions:
- not:
- columnExists:
tableName: users
columnName: status
changes:
- addColumn:
tableName: users
columns:
- column:
name: status
type: VARCHAR(20)
Using SQL Files
For complex migrations:
databaseChangeLog:
- changeSet:
id: 007
author: developer
changes:
- sqlFile:
path: db/changelog/sql/007-migration.sql
007-migration.sql:
-- liquibase formatted sql
-- changeset developer:007
UPDATE users SET status = 'ACTIVE' WHERE status IS NULL;
CREATE INDEX idx_users_status ON users(status);
-- rollback DROP INDEX idx_users_status;
-- rollback UPDATE users SET status = NULL WHERE status = 'ACTIVE';
Data Migration
Update existing data:
databaseChangeLog:
- changeSet:
id: 008
author: developer
changes:
- update:
tableName: users
columns:
- column:
name: status
value: LEGACY
where: created_at < '2020-01-01'
Load Data from CSV
databaseChangeLog:
- changeSet:
id: 009
author: developer
changes:
- loadData:
tableName: categories
file: db/data/categories.csv
columns:
- column:
name: name
type: STRING
- column:
name: description
type: STRING
Creating Indexes
databaseChangeLog:
- changeSet:
id: 010
author: developer
changes:
- createIndex:
indexName: idx_username
tableName: users
columns:
- column:
name: username
Common Change Types
| Change Type | Purpose |
|---|---|
| createTable | Create new table |
| dropTable | Remove table |
| addColumn | Add column |
| dropColumn | Remove column |
| modifyDataType | Change column type |
| renameColumn | Rename column |
| createIndex | Add index |
| addForeignKeyConstraint | Add foreign key |
| insert | Insert data |
| update | Update data |
| sql | Execute custom SQL |
Programmatic Configuration
@Configuration
public class LiquibaseConfig {
@Bean
public SpringLiquibase liquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:db/changelog/db.changelog-master.yaml");
liquibase.setContexts("dev,prod");
return liquibase;
}
}
Best Practices
Never modify executed changesets. Use meaningful changeset IDs like 001, 002 or dates. Include author in every changeset. Use contexts for environment-specific changes. Always define rollback for production. Test migrations in development first. Keep changesets small and focused. Add preconditions to prevent errors. Document complex migrations with comments.
Troubleshooting
Clear checksums after manual fixes:
-- View changelog table
SELECT * FROM databasechangelog;
-- Clear checksums
UPDATE databasechangelog SET md5sum = NULL;
Mark changeset as executed manually:
INSERT INTO databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, EXECTYPE)
VALUES ('011', 'developer', 'changelog.yaml', NOW(), 11, 'EXECUTED');
Liquibase vs Flyway
| Feature | Liquibase | Flyway |
|---|---|---|
| File Formats | XML, YAML, JSON, SQL | SQL primarily |
| Rollback | Built-in | Paid feature |
| Database Agnostic | Excellent | Good |
| Learning Curve | Moderate | Easy |
| Preconditions | Yes | No |
When to Use Liquibase
Choose Liquibase when you need database portability across MySQL, PostgreSQL, Oracle. Need built-in rollback capabilities. Want to use YAML or XML instead of SQL. Require conditional migrations with contexts. Need preconditions for safety checks. Working on complex enterprise applications.
Top comments (0)