The liquibase-changelog-generator library implements an auto-generation of Liquibase changelogs
based on the JPA/Hibernate entities. 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.1</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.Entityclasses. - 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.xmlThe library also supports pgvector in case you use the hibernate-vector module for VECTOR columns.
We provide the two configuration classes HibernatePopulatedConfigForPgVector and LiquibasePopulatedConfigForPgVector
that take care of spinning-up a pgvector database with the vector extension pre-installed.
@Entity
class ExampleEntity {
@Id
private Long id;
@Column
@JdbcTypeCode(SqlTypes.VECTOR)
@Array(length = 768)
private float[] vectorData;
}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 HibernatePopulatedConfigForPgVector {
}
@PropertySource("classpath:/liquibase-test-liquibase.properties")
static class LiquibasePopulatedConfig extends LiquibasePopulatedConfigForPgVector {
}
}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
@Entityclass 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
LiquibaseTestand 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=legacyThis 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.5.6+
- Liquibase 4.31.1+
- Hibernate 6.6.29+