将子类转换为PostgreSQL jsonb类型时遇Hibernate异常求助
核心问题定位
异常IllegalStateException: Convert placed on Embedded attribute must define (sub)attributeName的根源是:你在@Embedded注解的字段上直接使用@Convert,但@Embedded用于将嵌入类的属性映射为数据库表的独立列,而你需要将整个嵌入类对象序列化为JSON存储到单个jsonb列,两者用法冲突。
分步修复
1. 修正实体类注解
移除content字段上的@Embedded注解,保留@Convert和@Column,确保整个SubClass对象被转换为JSON字符串存储到jsonb列,同时修复代码中的低级错误:
@Entity @Table(name = "my_table") public class MyClassEntity { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name = "id", nullable = false) private UUID id; @Column(name = "description", nullable = false) private String description; @Convert(converter = SubClassConverter.class) @Column(name = "content", columnDefinition = "jsonb") private SubClass content; public void setId(UUID id) { this.id = id; } public UUID getId() { return this.id; } // 修正拼写错误:setDesctiption -> setDescription public void setDescription(String description) { this.description = description; } public String getDescription() { return this.description; } // 修正语法错误:补全参数括号 public void setContent(SubClass content) { this.content = content; } public SubClass getContent() { return this.content; } }
2. 修复转换器方法名错误
转换器类中convertToDatabaseColumn方法名拼写错误,修正为正确的方法名:
public class SubClassConverter implements AttributeConverter<SubClass, String> { private ObjectMapper objectMapper = new ObjectMapper(); public SubClassConverter() { objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @Override // 修正方法名:移除多余的SubClassConverter后缀 public String convertToDatabaseColumn(SubClass in) { try { return objectMapper.writeValueAsString(in); } catch (Exception e) { throw new RuntimeException("Failed to convert SubClass to JSON", e); } } @Override public SubClass convertToEntityAttribute(String joined) { if (StringUtils.isEmpty(joined)) { return null; } try { return objectMapper.readValue(joined, SubClass.class); } catch (IOException e) { throw new RuntimeException("Failed to convert JSON to SubClass", e); } } }
3. 优化仓库层实现
原生查询中不需要手动cast(:content as jsonb),转换器已将SubClass转为JSON字符串,PostgreSQL会自动识别并转换为jsonb类型。推荐直接使用CrudRepository的save方法,避免手写SQL的冗余:
@Repository public interface MyClassRepository extends CrudRepository<MyClassEntity, UUID> { // 若坚持使用原生查询,修正参数类型为String(转换器输出类型) @Modifying @Transactional @Query(value = "insert into my_table (description, content) values (:description, :content)", nativeQuery = true) void insertMyClass(@Param("description") String description, @Param("content") String content); }
使用save方法时,直接调用myClassRepository.save(myClassEntity)即可,Hibernate会自动通过转换器处理content字段
可选方案:使用Hibernate原生JsonType(推荐)
若项目使用Hibernate 5.2+,可直接使用@Type注解指定JsonType,无需自定义转换器:
@Entity @Table(name = "my_table") public class MyClassEntity { // ...其他字段 @Type(type = "jsonb") @Column(name = "content", columnDefinition = "jsonb") private SubClass content; // ...getter/setter }
移除自定义的SubClassConverter后,Hibernate会自动通过Jackson完成对象与JSON的转换。
验证步骤
- 确认所有注解修正、语法错误修复完成
- 启动应用并测试写入操作,确认异常消失
- 查询数据库,验证
content列存储的是正确的JSON格式数据
内容的提问来源于stack exchange,提问作者user1673206

