Search code examples
postgresqlhibernatequarkushibernate-reactive

Using the PostgreSQL JSONB type with Hibernate Reactive


I am migrating my Quarkus project from the classic Hibernate ORM to Hibernate Reactive and I faced a problem with JSONB field mapping.

Here is the entity:

@Entity
@TypeDef(name = JsonTypes.JSON_BIN, typeClass = JsonBinaryType::class)
class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "myEntityIdGenerator")
    @SequenceGenerator(name = "myEntityIdGenerator", sequenceName = "my_entity_id_seq", allocationSize = 10)
    var id: Long? = null

    // Usage of a plain JsonNode instead of a mapped class is intentional, 
    // as the app receives a request with raw JSON data and should store it without any processing
    @Type(type = JsonTypes.JSON_BIN)
    @NotNull
    lateinit var jsonData: JsonNode
}

The project has the io.quarkiverse.hibernatetypes:quarkus-hibernate-types:0.2.0 dependency to handle JSON types.

This code worked fine with blocking Hibernate API, but when trying to persist a MyEntity using the Hibernate Reactive, I get the following exception:

io.vertx.core.impl.NoStackTraceThrowable: Parameter at position[1] with class = [com.fasterxml.jackson.databind.node.ObjectNode] and value = [{"field1":"some value"}] can not be coerced to the expected class = [java.lang.Object] for encoding.

Is this a bug or custom types should be handled differently while using Hibernate Reactive?


Solution

  • Hibernate Types is not compatible with Hibernate Reactive.

    But you have three options to map a Json with Hibernate Reactive:

    1. Use io.vertx.core.json.JsonObject
    2. Map it as String and use a converter
    3. Create a UserType

    1. JsonObject

    Example with io.vertx.core.json.JsonObject:

        @Entity
        private static class EntityWithJson {
    
            ...
    
            private JsonObject jsonObj;
    
    ...
    }
    

    You can see a working example in the repository: JsonTypeTest

    2. Using a converter

    Example using a converter:

    class EntityWithJson {
    
            @Column(columnDefinition = "json")
            @Convert(converter = StringToJson.class)
            private String json;
    ...
    }
    
    @Converter
    public class StringToJson implements AttributeConverter<String, JsonObject> {
    
        @Override
        public JsonObject convertToDatabaseColumn(String string) {
            if (string == null) {
                return null;
            }
            return new JsonObject(string);
        }
    
        @Override
        public String convertToEntityAttribute(JsonObject dbData) {
    
            if (dbData == null) {
                return null;
            }
            return dbData.encodePrettily();
        }
    }
    

    You can see a working example in the repository: JsonTypeTest

    3. UserType

    class EntityWithJson {
    
    
            @Type(type="org.example.Json")
            @Column(columnDefinition = "json")
            private JsonObject jsonObj;
    
    }
    
    package org.example
    
    public class Json implements UserType {
    
       // ... Implementation left out for brevity
    }
    

    You can see a working example in the repository: UserJsonTypeTest