Hibernate List字段映射异常:Oracle CLOB JSON解析问题求助
问题排查与解决
核心原因
@Formula注解仅支持映射单个基础数据类型(如String、Integer),无法直接将Oracle JSON函数返回的数组转换为List<KeyValue>这类自定义对象集合。Hibernate没有内置的转换逻辑,因此抛出类型无法确定的映射异常。
可行解决方案
方法1:自定义Hibernate类型(推荐)
实现自定义类型处理CLOB中JSON数组到List<KeyValue>的转换:
- 编写自定义类型类,实现
UserType接口,完成JSON解析与序列化:public class KeyValueListType implements UserType { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public int[] sqlTypes() { return new int[]{Types.CLOB}; } @Override public Class<?> returnedClass() { return List.class; } @Override public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws SQLException { Clob clob = rs.getClob(names[0]); if (clob == null) return Collections.emptyList(); try (Reader reader = clob.getCharacterStream()) { JsonNode rootNode = objectMapper.readTree(reader); JsonNode keyValueNode = rootNode.get("keyvalue"); if (keyValueNode == null || keyValueNode.isNull()) return Collections.emptyList(); return objectMapper.treeToValue(keyValueNode, new TypeReference<List<KeyValue>>() {}); } catch (IOException e) { throw new SQLException("JSON解析失败", e); } } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws SQLException { if (value == null) { st.setNull(index, Types.CLOB); return; } try { String jsonStr = objectMapper.writeValueAsString(value); st.setClob(index, new StringClob(session.getJdbcServices().getJdbcEnvironment().getDialect(), jsonStr)); } catch (IOException e) { throw new SQLException("JSON序列化失败", e); } } // 重写deepCopy、equals、hashCode等剩余必要方法 } - 在实体类中用
@Type注解指定自定义类型,直接映射CLOB列:@Entity @Table(name = "MYTABLE") public class MyEntity { @Id private Long id; @Column(name = "myclob_column") @Type(type = "com.yourpackage.KeyValueListType") private List<KeyValue> keyvalue; // 其他字段与方法 }
方法2:@PostLoad手动解析
通过加载后回调手动解析JSON数组:
- 实体类中保留CLOB字段,添加
@Transient标记的集合字段:
注意:@Entity @Table(name = "MYTABLE") public class MyEntity { @Id private Long id; @Column(name = "myclob_column") private Clob myclobColumn; @Transient private List<KeyValue> keyvalue; @PostLoad private void parseKeyValue() { if (myclobColumn == null) { keyvalue = Collections.emptyList(); return; } ObjectMapper objectMapper = new ObjectMapper(); try (Reader reader = myclobColumn.getCharacterStream()) { JsonNode rootNode = objectMapper.readTree(reader); JsonNode keyValueNode = rootNode.get("keyvalue"); keyvalue = objectMapper.treeToValue(keyValueNode, new TypeReference<List<KeyValue>>() {}); } catch (IOException | SQLException e) { throw new RuntimeException("解析JSON数组失败", e); } } // 其他字段与方法 }@Transient标记的字段不会被Hibernate持久化,仅在实体加载后填充。
方法3:JSON_TABLE配合@OneToMany(复杂查询场景)
利用Oracle的JSON_TABLE将数组行转列,通过关联映射实现:
- 编写基于JSON_TABLE的查询逻辑:
SELECT t.id, kv.key, kv.value FROM MYTABLE t, JSON_TABLE(t.myclob_column, '$.keyvalue[*]' COLUMNS key VARCHAR2(100) PATH '$.key', value VARCHAR2(100) PATH '$.value' ) kv - 定义KeyValue实体类,在MyEntity中配置关联:
此方式适合需要单独查询keyvalue元素的场景,但配置复杂度较高。@Entity public class MyEntity { @Id private Long id; @OneToMany @JoinColumn(name = "id") @Formula("(SELECT kv.id FROM MYTABLE t, JSON_TABLE(t.myclob_column, '$.keyvalue[*]' COLUMNS key VARCHAR2(100) PATH '$.key', value VARCHAR2(100) PATH '$.value') kv WHERE t.id = id)") private List<KeyValue> keyvalue; // 其他字段 }
注意事项
- 确保项目引入JSON解析依赖(如Spring Boot的
spring-boot-starter-json)。 - 自定义类型需妥善处理空值与异常,避免实体加载时抛出未捕获错误。
- 确认Oracle版本(12c及以上)已开启JSON支持(默认开启)。
内容的提问来源于stack exchange,提问作者Surya
相关产品推荐
相关产品推荐

