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

Hibernate用@JdbcTypeCode(SqlTypes.JSON)对接Oracle BLOB报ORA-17004错误

适配Oracle BLOB存储JSON的Hibernate解决方案

问题本质

使用@JdbcTypeCode(SqlTypes.JSON)映射PostgreSQL JSONB列时,Hibernate会自动适配JSON类型的读写逻辑;但切换到Oracle的BLOB列时,Hibernate仍尝试以字符串方式读取BLOB数据,而Oracle的T4CBlobAccessor不支持getString操作,因此抛出ORA-17004错误。

解决方案(无需修改业务逻辑)

1. 自定义跨数据库兼容的JDBC类型

创建自定义JDBC类型,针对Oracle处理BLOB与JSON的转换,同时兼容PostgreSQL的JSONB:

import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.MapJavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;
import org.hibernate.type.descriptor.jdbc.JdbcTypeIndicators;
import org.hibernate.type.descriptor.jdbc.VarbinaryJdbcType;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.Map;

public class JsonBlobJdbcType extends VarbinaryJdbcType {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    @Override
    public JdbcType resolveIndicatedType(JdbcTypeIndicators indicators, org.hibernate.type.descriptor.java.JavaType<?> domainJtd) {
        // 根据方言自动切换适配逻辑
        String dialect = indicators.getConfiguration().getProperties().getProperty("hibernate.dialect");
        if (dialect != null && dialect.contains("PostgreSQL")) {
            return indicators.getTypeConfiguration().getJdbcTypeRegistry().getDescriptor(SqlTypes.JSON);
        }
        return this;
    }

    @Override
    public <X> X getNullableValue(java.sql.ResultSet rs, String columnName, WrapperOptions options) throws SQLException {
        Blob blob = rs.getBlob(columnName);
        if (blob == null || blob.length() == 0) {
            return null;
        }
        try {
            byte[] bytes = blob.getBytes(1, (int) blob.length());
            Map<String, Object> map = OBJECT_MAPPER.readValue(bytes, Map.class);
            return (X) map;
        } catch (IOException e) {
            throw new SQLException("解析BLOB到JSON失败", e);
        } finally {
            blob.free();
        }
    }

    @Override
    public void setNonNullParameter(java.sql.PreparedStatement st, int index, Object value, WrapperOptions options) throws SQLException {
        try {
            byte[] bytes = OBJECT_MAPPER.writeValueAsBytes(value);
            st.setBlob(index, new ByteArrayInputStream(bytes));
        } catch (IOException e) {
            throw new SQLException("序列化JSON到BLOB失败", e);
        }
    }
}

2. 实体类配置调整

替换原有的@JdbcTypeCode注解,改用自定义类型:

@Type(value = JsonBlobJdbcType.class)
@Column(name = "user_addl_info")
private Map<String, Object> additionalInfo;

如果需要动态适配不同数据库的列定义,可通过Hibernate方言或配置文件的profile机制,在打包时自动替换columnDefinition属性(比如Oracle用BLOB,PostgreSQL用JSONB)。

3. 全局类型注册(可选,Hibernate 6+)

若想避免在每个实体类中重复指定类型,可通过类型贡献者全局注册:

import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.service.ServiceRegistry;

public class JsonBlobTypeContributor implements TypeContributor {
    @Override
    public void contribute(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
        JsonBlobJdbcType jsonBlobType = new JsonBlobJdbcType();
        typeContributions.contributeJdbcType(jsonBlobType);
        // 注册Map类型与自定义JDBC类型的绑定
        typeContributions.contributeType(
            new org.hibernate.type.MapType(
                "json-blob",
                typeContributions.getTypeConfiguration().getJavaTypeRegistry().getDescriptor(Map.class),
                jsonBlobType
            )
        );
    }
}

然后在配置文件中指定类型贡献者:

hibernate.type_contributors=com.yourpackage.JsonBlobTypeContributor

最佳实践

  • 统一使用Jackson作为JSON序列化工具,避免不同序列化框架导致的兼容性问题。
  • 测试阶段覆盖PostgreSQL和Oracle两种场景,验证读写、空值、复杂JSON结构的兼容性。
  • 优先基于Hibernate 6+的类型系统实现,其对多数据库适配的支持更灵活。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:47:43