The liquibase-changelog-generator
library implements an auto-generation of Liquibase changelogs
based on the Hibernate metamodel. The library was designed to be used in a JUnit test.
Depending on the database, you need to add the following Maven test dependency to your project:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>liquibase-changelog-generator-postgresql</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
This library provides a mechanism to implement a unit test that sets up two databases using Testcontainers:
- Hibernate Database: Uses the database schema populated by Hibernate, based on the annotations of the
jakarta.persistence.Entity
classes. - Liquibase Database: Uses the Liquibase changelog to populate the database schema.
Once both databases are ready, we use the DiffToChangeLog mechanism of Liquibase to compare the two databases. If the diff is non-empty, the test fails and it outputs the required Liquibase changes. This ensures that the build pipeline can only succeed if there are no missing changesets. We recommend to assert that diff using our validation-file-assertions library.
class LiquibaseTest implements JUnit5ValidationFileAssertions {
@Test
void testLiquibaseAndHibernatePopulationsAreConsistent() {
HibernateToLiquibaseDiff hibernateToLiquibaseDiff = new HibernateToLiquibaseDiffForPostgres("My Author");
String diff = hibernateToLiquibaseDiff.generateDiff(HibernatePopulatedConfig.class, LiquibasePopulatedConfig.class);
assertWithFile(diff, FileExtensions.XML);
}
@EntityScan("de.cronn.example")
static class HibernatePopulatedConfig extends HibernatePopulatedConfigForPostgres {
}
@PropertySource("classpath:/liquibase-test-liquibase.properties")
static class LiquibasePopulatedConfig extends LiquibasePopulatedConfigForPostgres {
}
}
Then define the path to the Liquibase changelog via src/test/resources/liquibase-test-liquibase.properties
spring.liquibase.change-log=classpath:/migrations/changelog.xml
As a developer, you typically follow these steps when you want to change or extend the database schema:
- Modify Entity Classes: Create, modify, or delete a
@Entity
class as desired. - Run the Test: Execute the
LiquibaseTest
. The test will fail and output the generated Liquibase changeset in the form of a difference to the validation file. - Update Changelog: Take the generated Liquibase changeset and add it to the Liquibase changelog file(s).
- Review Changeset: ⚠ Review the auto-generated changeset very carefully! Consider it as only a suggestion or a template. The Liquibase diff mechanism is not perfect. For instance, when renaming a column, it will yield a drop-column and a create-column statement. In such cases, you need to adjust the changeset manually.
- Re-run the Test: Re-run the
LiquibaseTest
and check that the test now succeeds.
Unfortunately, Hibernate's schema generation applies an alphabetical ordering to all columns. This can result in incorrect column order when using composite primary keys. Our bug report HHH-17065 was closed as "Won’t Fix" by the Hibernate team.
Fortunately, there’s a workaround: you can switch the column order strategy back to the previous behavior:
# This is a workaround for a composite primary key column order regression in Hibernate 6.2
# See https://hibernate.atlassian.net/browse/HHH-17065
spring.jpa.properties.hibernate.column_ordering_strategy=legacy
This property only needs to be set for the configuration class used in the Hibernate-populated Spring context:
@EntityScan("de.cronn.example")
@PropertySource("classpath:/liquibase-test-hibernate.properties")
static class HibernatePopulatedConfig extends HibernatePopulatedConfigForPostgres {
}
The test performs some automatic post-processing of the diff. For example, it overrides the Hibernate-generated
primary-key and foreign-key names. See the HibernateToLiquibaseDiff.filterDiffResult(DiffResult)
method for details.
We also provide a utility class to export the schema that Hibernate would create. This can be useful as a first feedback during a (major) change of the JPA entities.
class HibernateSchemaTest implements JUnit5ValidationFileAssertions {
@Test
void testExport() {
String schema = new HibernateSchemaExport(HibernatePopulatedConfig.class).export();
assertWithFile(schema, FileExtensions.SQL);
}
@EntityScan("de.cronn.example")
static class HibernatePopulatedConfig extends HibernatePopulatedConfigForPostgres {
}
}
- Java 17+
- Spring Boot 3.3.1+
- Liquibase 4.27.0+
- Hibernate 6.5.2+