The following best practices provide guidance on the primary Liquibase components and workflow.
Organize your Changelogs
1. Define the directory structure
The most common way to organize changelogs is by major release.
Make sure to store your changelogs in source control, preferably near your database access code
In this example, we will use com/example/db/changelog
.
com example db changelog db.changelog-root.xml db.changelog-1.0.xml db.changelog-1.1.xml db.changelog-2.0.xml DatabasePool.java AbstractDAO.java
2. Set up the root changelog and included changelog files
The db.changelog-root.xml
file will automatically include the changelogs for each release in the alpha-numeric order.
Note: The db.changelog-root.xml
file is the changelog name that you will pass to all Liquibase calls.
For the example above, the changelog would look like the following:
<?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" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd"> <includeAll path="com/example/db/changelog/"/> </databaseChangeLog>
The included changelogs can be in any format (including formatted SQL). Choose the appropriate format that best fits you and your team’s situation.
Use a SQL changelog when your team
- Is already familiar with SQL
- Wants to get the benefits of using Liquibase without needing to learn a new way to specify database changes
Use a Model-based changelog (XML, JSON, or YAML) when your team
- Wants to deploy a single set of changes to different types of databases
- Wants to take advantage of automated
rollback
of new database objects - Wants to take advantage of Liquibase
diff
anddiffChangelog
functionality
Here are examples of the different changelog formats for the db.changelog-1.0
file:
--liquibase formatted sql --changeset nvoxland:1 create table person ( id int primary key, name varchar(255) );
<?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" xmlns:pro="http://www.liquibase.org/xml/ns/pro" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd http://www.liquibase.org/xml/ns/pro http://www.liquibase.org/xml/ns/pro/liquibase-pro-4.1.xsd"> <changeSet author="nvoxland" id="1"> <createTable tableName="person"> <column name="id" type="INTEGER"> <constraints nullable="false" primaryKey="true" unique="true"/> </column> <column name="name" type="VARCHAR(255)" /> </createTable> </changeSet> </databaseChangeLog>
{ "databaseChangeLog": [ { "changeSet": { "id": "1", "author": "nvoxland", "changes": [ { "createTable": { "tableName": "person", "columns": [ { "column": { "name": "id", "type": "int", "autoIncrement": true, "constraints": { "primaryKey": true, "nullable": false }, } }, { "column": { "name": "name", "type": "varchar(255)" } } ] } } ] } } ] }
databaseChangeLog: - preConditions: - runningAs: username: liquibase - changeSet: id: 1 author: nvoxland changes: - createTable: tableName: person columns: - column: name: id type: int autoIncrement: true constraints: primaryKey: true nullable: false - column: name: name type: varchar(255)
Establish team changelog standards
1. One change per changeset
We strongly encourage having only one change per changeset. This makes each change atomic within a single transaction. Each changeset either succeeds or fails. If it fails, it can be corrected and redeployed until it succeeds. Multiple independent changes in one changeset create a risk that some changes deploy while a later change fails. This leaves the database in a partially deployed state which requires manual intervention to correct.
The exception is when you have several changes you want to be grouped as a single transaction – in that case, multiple statements in the changeset if the correct choice.
2. Define the team’s changeset ID format
Choose a changeset ID that works for you. While it can be any string, we advise using an increasing number sequence starting at 1. Remember that each changeset ID needs to be unique within the changelog.
3. Document unclear or complicated changesets
Most of the time, changesets are self-documenting.
However, remember to use <comments>
for any changesets where you need to explain non-obvious or complicated database changes to other developers.
4. Have a rollback plan
Write changesets so they work with Liquibase rollback.
- Use a relevant Liquibase change type instead of using a custom
<sql>
tag. - Include a Liquibase rollback tag (
<rollback>
) whenever a change doesn’t support an out-of-box rollback. (e.g.,<sql>
,<insert>
, etc).
Make sure to test rollbacks in development to ensure the production rollback is safe and predictable.
5. Manage your reference data
Leverage Liquibase to manage your reference data. Environment separation (DEV, QA, PROD) can be achieved using Liquibase contexts. This functionality is helpful in the following situations:
- When you have test data that should only get deployed to QA environments
- When managing application configuration data – country table, application configuration data, etc
- When deplying data-fixes specific to the pre-production and production environments
Manage stored logic
Typically, changesets in Liquibase remain untouched after they are deployed. Subsequent database changes are made when new ‘roll forward’ changesets are added to the end of the changelog.
However, stored logic (Stored Procedures, Functions, Packages, Triggers, etc.) acts more like application code than database schema changes. They are better managed similar to source code where you continually make updates to a single source file for each unit of stored logic. This also allows you to better see changes over time using standard git tools.
We recommend maintaining a separate changelog dedicated to stored logic. The combination of “create or replace” in your stored logic sql along with runOnChange="true"
as an attribute on the changeset allows you to ensure changes get deploy when and only when a file changes. Specifically, setting this flag forces Liquibase to check if the changeset was modified. When it detects a modification, Liquibase deploys the (updated) change.
Develop software using this standard workflow
1. Using your favorite IDE or editor, create a new local changeset containing the change
2. Run liquibase update
to execute the new changeset
3. Perform the corresponding changes in the application code
4. Test the new application code together with the database change
5. Commit both the changeset and the application code to source control