This is part one in a three-part series about Liquibase, a database changeset management tool for Java applications. This section will cover introductory concepts, part two will cover basic Liquibase features, and part three will cover odd caveats that might be encountered while using Liquibase and how to work around them.
What Is Liquibase?
Using Java Persistence API (JPA) annotations to interact with a database in a Java project creates a unique problem that Liquibase is perfectly suited to solve. When creating a new JPA Entity class or modifying an existing one, new database tables, columns, and constraints will need to be created within each database the application uses. When dealing with a continuous integration or continuous delivery setup, this could include a separate database to modify for each developer machine, each QA environment, and the production environment. How can these database modifications be applied to each environment?
Two typical solutions exist—reliance on the ORM tool to automatically execute SQL generated from the JPA Entity classes to update the schema (such as Hibernate’s hibernate.hbm2ddl.auto=update property, which makes type assumptions and can’t rename or delete), or manually executing SQL written to correspond to the changes against each database. Given the limitations of ORM tool automatic schema modification, manual SQL is typically written for modifications to the production environment to guarantee the correct end state. But doesn’t this defeat the purpose of using automatic schema modification in non-production environments? Ideally the same technique would be used across all environments to reduce the chance of defects being missed, but manually executing SQL against every database can be time consuming, especially if the database types differ. What if there was a way to modify the schema in a database-agnostic fashion like it was a file under version control within the same commit that changes the JPA Entity code, and a way to automatically apply it when the application is deployed to each environment?
The answer is Liquibase, a file based database changeset tool written in Java that can be used alongside existing ORM solutions to manage all changes to the database each time the application is deployed. If Liquibase is unable to correctly apply a database change while deploying a new build in a continuous deployment setup, the change will be rolled back and the new build can be rejected until the issue is resolved. When used alongside Hibernate’s hibernate.hbm2ddl.auto=validate property, this can prevent invalid database changes from making it to QA and production environments while ensuring the schema is created the same way in each environment. As an added bonus, since it adds version control to each change made to the database, it’s easy to create the correct schema with its corresponding seed data from a blank database using Liquibase alone, making it easier to spin up new developer environments and QA servers.
Liquibase can execute database changes by being run from within an application as it boots (e.g. as a Spring or CDI bean) which works well for development builds; or as a separate JAR which can be driven by the command line, ant, or Maven for integration into continuous deployment processes. See the Liquibase Quickstart for configuration details.
When Liquibase runs, there are several commands it can execute. The most important one is the update command, which applies database changes from a file. Liquibase supports XML, JSON, and YAML files. This blog series will assume XML files are used due to the advantages provided by their schema validation capabilities. There are also commands for rolling back changes, generating SQL instead of applying changes, and generating documentation. Each method of executing Liquibase requires one of these commands to be specified, along with additional arguments (such as the location of relevant XML files).
The Liquibase update command reads the changes it needs to make from an XML file called a changelog. The changelog contains basic configuration settings, references to other changelog files, and multiple changeset elements all within its root databaseChangeLog element.
Changesets are the basic units of Liquibase. They can contain one or more database modifications using a database-agnostic XML format or inline SQL. The sample changeset below creates a basic table.
Using an IDE that is compatible with XSD auto-completion, creating new changesets is an easy combination of looking up the correct elements from the documentation and using auto complete to see what your available options are. The official Liquibase documentation makes it easy to see which elements are optional and required.
An alternative way to create the table without the database-agnostic syntax is:
CREATE TABLE HOUSE (ID VARCHAR(36) NOT NULL, ADDRESS VARCHAR(36), CONSTRAINT PK_HOUSE PRIMARY KEY (ID));
The SQL within this second example was derived from its XML version by executing the Liquibase updateSQL command, which generates SQL scripts that perform the same actions the Liquibase update command would have done directly. This can be useful if your process requires a DBA to approve scripts or if you need additional debugging help.
To Be Continued!
The next part of this blog series will provide a deeper dive into basic Liquibase concepts, and the final part will cover error recovery. Be sure to stay tuned with us on LinkedIn or follow our insights for further updates.