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
相关产品推荐
相关产品推荐

