You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3.2.4中PostgreSQL JSONb与JsonNode映射启动报错求助

问题排查与解决办法

问题根源

Spring Boot 3.2.4 依赖的是 Hibernate 6.4.x,这个版本对类型系统做了彻底重构,旧的UserType接口已经被标记为过时,而且Hibernate 6的类型解析逻辑和旧版完全不一样。你自定义的JsonNodeUserType是基于旧的UserType实现的,没法被Hibernate 6正确识别,导致系统找不到对应的JDBC类型(也就是PostgreSQL的jsonb),所以才抛出这个错误。

解决办法

方案1:用Hibernate原生JSON类型支持(优先推荐)

Hibernate 6已经原生支持把Jackson的JsonNode直接映射到PostgreSQL的jsonb列,完全不用自己写自定义类型,步骤很简单:

  1. 修改实体类的字段注解
    删掉之前自定义类型的配置,改用Hibernate的@JdbcTypeCode注解指定JSON类型代码,同时保留columnDefinition确保数据库里的列是jsonb类型:

    import com.fasterxml.jackson.databind.JsonNode;
    import jakarta.persistence.Column;
    import jakarta.persistence.Entity;
    import jakarta.persistence.Id;
    import org.hibernate.annotations.JdbcTypeCode;
    import org.hibernate.type.SqlTypes;
    
    @Entity
    public class MapEntity {
        @Id
        private Long id;
    
        @Column(columnDefinition = "jsonb")
        @JdbcTypeCode(SqlTypes.JSON)
        private JsonNode data;
    
        // 构造器、getter、setter自行补充
    }
    
  2. 清理旧的自定义类型相关代码

    • 删除你编写的JsonNodeUserType类
    • 移除application.properties里注册Hibernate类型的配置(比如类似spring.jpa.properties.hibernate.type_contributors这类配置)

方案2:适配Hibernate 6重构自定义类型(仅需兼容旧代码时使用)

如果一定要保留自定义类型的逻辑,就得改用Hibernate 6的CompositeUserType接口来实现,示例代码如下:

  1. 实现CompositeUserType接口

    import com.fasterxml.jackson.databind.JsonNode;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.hibernate.engine.spi.SharedSessionContractImplementor;
    import org.hibernate.type.CompositeUserType;
    import org.hibernate.type.SqlTypes;
    
    import java.io.Serializable;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Types;
    import java.util.Objects;
    
    public class JsonNodeUserType implements CompositeUserType<JsonNode> {
        private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    
        @Override
        public String[] getPropertyNames() {
            return new String[]{"data"};
        }
    
        @Override
        public org.hibernate.type.Type[] getPropertyTypes() {
            return new org.hibernate.type.Type[]{org.hibernate.type.JsonType.INSTANCE};
        }
    
        @Override
        public Object getPropertyValue(JsonNode component, int property) {
            return component;
        }
    
        @Override
        public void setPropertyValue(JsonNode component, int property, Object value) {
            // JsonNode是不可变类型,无需处理
        }
    
        @Override
        public Class<JsonNode> returnedClass() {
            return JsonNode.class;
        }
    
        @Override
        public boolean equals(JsonNode x, JsonNode y) {
            return Objects.equals(x, y);
        }
    
        @Override
        public int hashCode(JsonNode x) {
            return Objects.hashCode(x);
        }
    
        @Override
        public JsonNode nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException {
            String json = rs.getString(names[0]);
            if (json == null) {
                return null;
            }
            try {
                return OBJECT_MAPPER.readTree(json);
            } catch (Exception e) {
                throw new SQLException("解析JSON失败", e);
            }
        }
    
        @Override
        public void nullSafeSet(PreparedStatement st, JsonNode value, int index, SharedSessionContractImplementor session) throws SQLException {
            if (value == null) {
                st.setNull(index, Types.OTHER);
            } else {
                try {
                    String json = OBJECT_MAPPER.writeValueAsString(value);
                    st.setObject(index, json, SqlTypes.JSON);
                } catch (Exception e) {
                    throw new SQLException("序列化JSON失败", e);
                }
            }
        }
    
        @Override
        public JsonNode deepCopy(JsonNode value) {
            return value == null ? null : value.deepCopy();
        }
    
        @Override
        public boolean isMutable() {
            return false;
        }
    
        @Override
        public Serializable disassemble(JsonNode value) {
            return deepCopy(value);
        }
    
        @Override
        public JsonNode assemble(Serializable cached, Object owner) {
            return deepCopy((JsonNode) cached);
        }
    
        @Override
        public JsonNode replace(JsonNode original, JsonNode target, Object owner) {
            return deepCopy(original);
        }
    }
    
  2. 注册自定义类型
    在application.properties中添加类型贡献者配置:

    spring.jpa.properties.hibernate.type_contributors=你的包路径.JsonNodeTypeContributor
    

    然后实现TypeContributor接口:

    import org.hibernate.boot.model.TypeContributions;
    import org.hibernate.boot.model.TypeContributor;
    import org.hibernate.service.ServiceRegistry;
    
    public class JsonNodeTypeContributor implements TypeContributor {
        @Override
        public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
            typeContributions.contributeType(new JsonNodeUserType());
        }
    }
    
  3. 修改实体类字段注解
    指定使用自定义类型:

    @Column(columnDefinition = "jsonb")
    @Type(JsonNodeUserType.class)
    private JsonNode data;
    

验证

启动应用后,尝试执行插入或查询操作,确认JsonNode数据能正常存入PostgreSQL的jsonb列,且无类型相关错误。

内容的提问来源于stack exchange,提问作者Serge

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:57:08