Search code examples
spring-data-jpaliquibaseliquibase-hibernateliquibase-maven-plugin

JSONB usage with liquibase-hibernate5 not supported through liquibase maven plugin?


I am trying to get JSONB type working with liquibase-hibernate5 extension, but without any luck. I am using liquibase core and the extension version 4.10. I am using liquibase within Spring boot 2.5.14. I have added my own dialect file of Postgres to add support for JSONB:

public class PostgreSQLDialect extends PostgreSQL10Dialect {

public PostgreSQLDialect() {
    // Support for JsonB, else the liquibase mvn plugin will fail.
    // Ref: https://thorben-janssen.com/persist-postgresqls-jsonb-data-type-hibernate/
    System.out.println("Registering the JSONB type.");
    this.registerColumnType(Types.JAVA_OBJECT, "jsonb");
}

}

And registered it in the application.yml file:

spring.jpa.properties.hibernate.dialect: my.example.PostgreSQLDialect

And also added it to the liquibase ref URL in the pom.xml

<referenceUrl>hibernate:spring:my.example.persist?dialect=my.exampple.PostgreSQLDialect&amp;hibernate.physical_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy&amp;hibernate.implicit_naming_strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl</referenceUrl>

But still when running the mvn liquibase:diff command it shows in the output it's using the Dialect class, and also registers the jsonb file, but keeps complaining that it can't load the class:

Caused by: java.lang.ClassNotFoundException: Could not load requested class : jsonb

Anybody any idea? I bounced my head a few times against this, and always worked around it by temp commenting the jsonb usages in the code, but would really love to solve this.

More about the stacktrace:

Caused by: java.lang.ClassNotFoundException: Could not load requested class : jsonb
    at org.hibernate.boot.registry.classloading.internal.AggregatedClassLoader.findClass (AggregatedClassLoader.java:210)
    at java.lang.ClassLoader.loadClass (ClassLoader.java:589)
    at java.lang.ClassLoader.loadClass (ClassLoader.java:522)
    at java.lang.Class.forName0 (Native Method)
    at java.lang.Class.forName (Class.java:427)
    at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.classForName (ClassLoaderServiceImpl.java:130)
    at org.hibernate.boot.internal.ClassLoaderAccessImpl.classForName (ClassLoaderAccessImpl.java:67)
    at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue (SimpleValueBinder.java:536)
    at org.hibernate.cfg.SetSimpleValueTypeSecondPass.doSecondPass (SetSimpleValueTypeSecondPass.java:25)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses (InFlightMetadataCollectorImpl.java:1693)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses (InFlightMetadataCollectorImpl.java:1651)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete (MetadataBuildingProcess.java:295)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata (EntityManagerFactoryBuilderImpl.java:1224)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build (EntityManagerFactoryBuilderImpl.java:1255)
    at liquibase.ext.hibernate.database.HibernateEjb3Database.buildMetadataFromPath (HibernateEjb3Database.java:59)
    at liquibase.ext.hibernate.database.HibernateDatabase.buildMetadata (HibernateDatabase.java:143)
    at liquibase.ext.hibernate.database.HibernateDatabase.setConnection (HibernateDatabase.java:83)
.....

Solution

  • I did some debugging in the hibernate-types5 lib, as I use it to support extra db types in java. I found out that it does support the JSONB type but it does NOT inject (attribute to speak in Hibernate terms) the JsonBinaryType class to support the JSONB type.

    Why not? As it only adds the JsonBinaryType class in the class HibernateTypesContributor when your dialect extends from the deprecated org.hibernate.dialect.PostgreSQLDialect. I extend from one of the newer Postgres 10 Dialect classes, so hibernate-types don't inject them. BTW: the hibernate-types5 lib needs some serious updates, it's outdated.

    Solution: create your own PostgresDialect file and register it in the application.yml file or in the liquibase maven plugin if you use that, just like it did. And copy the code from HibernateTypesContributor and inject the Postgres-specific types by overriding the method contributeTypes in your dialect file.

    My dialect file:

    @Slf4j
    public class PostgreSQLDialect extends PostgreSQL10Dialect {
    
        public PostgreSQLDialect() {
            log.info("Using our own Postgres dialect.");
        }
    
        @Override
        public void contributeTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
            super.contributeTypes(typeContributions, serviceRegistry);
    
            // Copy of the class hiberate-types-52 HibernateTypesContributor, as its kind of old and doesn't add the types
            // below when not extending from the deprecated old PostgreSQLDialect file.
            contributeType(typeContributions, BooleanArrayType.INSTANCE)
                    .contributeType(typeContributions,
                            DateArrayType.INSTANCE)
                    .contributeType(typeContributions, DecimalArrayType.INSTANCE)
                    .contributeType(typeContributions, DoubleArrayType.INSTANCE)
                    .contributeType(typeContributions, EnumArrayType.INSTANCE)
                    .contributeType(typeContributions, IntArrayType.INSTANCE)
                    .contributeType(typeContributions, DoubleArrayType.INSTANCE)
                    .contributeType(typeContributions, ListArrayType.INSTANCE)
                    .contributeType(typeContributions, LocalDateArrayType.INSTANCE)
                    .contributeType(typeContributions, LocalDateTimeArrayType.INSTANCE)
                    .contributeType(typeContributions, LongArrayType.INSTANCE)
                    .contributeType(typeContributions, StringArrayType.INSTANCE)
                    .contributeType(typeContributions, TimestampArrayType.INSTANCE)
                    .contributeType(typeContributions, UUIDArrayType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLIntervalType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLPeriodType.INSTANCE)
                    .contributeType(typeContributions, JsonBinaryType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLTSVectorType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLEnumType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLHStoreType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLInetType.INSTANCE)
                    .contributeType(typeContributions, PostgreSQLRangeType.INSTANCE);
        }
    
        private PostgreSQLDialect contributeType(TypeContributions typeContributions, BasicType type) {
            typeContributions.contributeType(type);
            return this;
        }
    
        private PostgreSQLDialect contributeType(TypeContributions typeContributions, UserType type) {
            if (type instanceof ImmutableType) {
                var immutableType = (ImmutableType) type;
                typeContributions.contributeType(immutableType, new String[]{immutableType.getName()});
            } else {
                typeContributions.contributeType(type, new String[]{type.getClass().getSimpleName()});
            }
            return this;
        }
    }