Hibernate 6 and generic JSON field
Asked Answered
V

2

10

I am using PostgreSQL and Hibernate 6, and I have a JSONB type column - values.

The question is, how can I define an entity with generic type for this field? Also, is there any way to map the correct class during deserialization based on the second ENUM field - type?

Something like:

@Getter
@Setter
@Entity
@Table(name = "setting")
public class SettingEntity<T> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated(EnumType.STRING)
    private SettingType type;
    @JdbcTypeCode(SqlTypes.JSON)
    private T values;

}

I can't find a clear explanation of how to do that. Thanks for any assistance.

Validate answered 3/10, 2023 at 22:24 Comment(0)
I
2

Have you tried using the Jackson library to assist with object serialization?

Your code would look like this:

@Getter
@Setter
@Entity
@Table(name = "setting")
public class SettingEntity<T> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Enumerated(EnumType.STRING)
    private SettingType type;
    @Column(columnDefinition = "jsonb")
    @Convert(converter = JsonConverter.class)
    private T values;

}

Additionally, you'll need to create a converter to handle JSON conversion for a generic type:

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;

@Converter
public class JsonConverter<T> implements AttributeConverter<T, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(T attribute) {
        try {
            return objectMapper.writeValueAsString(attribute);
        } catch (IOException e) {
            throw new IllegalArgumentException("Error converting JSON to string", e);
        }
    }

    @Override
    public T convertToEntityAttribute(String dbData) {
        try {
            return objectMapper.readValue(dbData, new TypeReference<T>() {});
        } catch (IOException e) {
            throw new IllegalArgumentException("Error converting string to JSON", e);
        }
    }
}

In the above example, the JsonConverter class implements AttributeConverter using Jackson to serialize and deserialize JSON objects.

Don't forget to add the Maven dependency:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>VERSION</version> 
</dependency>

Or if you're using Gradle:

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:VERSION' 
} 

Remember to replace 'VERSION' with a version of Jackson compatible with your project!

Irrevocable answered 3/3 at 15:12 Comment(1)
I think this is probably the "correct" approach for Hibernate 6, although I was also able to get my code working by using @Type(io.hypersistence.utils.hibernate.type.json.JsonBinaryType.class), where that is the Hibernate 6 type.Immune
A
1

Basically, you will need

@Setter
@Entity
@Table(name = "setting")
public class SettingEntity<T> {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Enumerated(EnumType.STRING)
    private SettingType type;

    @Column(columnDefinition = "jsonb")
    @Type(type = "JsonbUserType")
    private T values;  
}

Now you need to implement a custom Hibernate UserType class with the necessary serialization and deserialization logic, you can effectively map custom data types in Hibernate.

public class JsonbUserType implements UserType {

@Override
public int[] sqlTypes() {
    return new int[]{Types.JAVA_OBJECT};
}

@Override
public Class returnedClass() {
    return YourCustomClass.class; // Define the Java class returned by this UserType
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
    // Implement logic to deserialize JSON data from the database
    return null; // Replace with your deserialization logic
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException {
    // Implement logic to serialize Java object to JSON for storage in the database
    // Example: st.setObject(index, value, Types.OTHER);
}

// Implement other required methods like deepCopy, equals, hashCode, etc.
 }

Also, you can check Vlad Mihalcea article on implementing a custom basic type using Hibernate UserType

Achaean answered 3/3 at 21:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.