JPA自定义类型转JSON存储报错:无法将final字段设为LinkedHashMap
问题根因
- 第一处错误在
JpaJsonConverter的实现:你将转换器泛型定义为AttributeConverter<Object, String>,反序列化时调用objectMapper.readValue(localizedTextsJSON, Object.class),Jackson遇到Object类型的反序列化目标时,默认会返回LinkedHashMap实例,和字段声明的LocalizedTexts类型完全不匹配。 - 第二处错误在实体类的Lombok注解选择:
@Value注解会默认将类中所有字段声明为final,本身JPA规范不推荐使用不可变类作为实体,因为Hibernate需要通过反射为字段赋值。你添加的@NoArgsConstructor(force = true)仅会强制为final字段初始化null默认值,后续Hibernate执行merge逻辑拷贝属性值时,既无法修改final字段,又遇到类型不匹配的LinkedHashMap值,直接抛出异常。 - 额外问题:你在转换器中捕获异常后没有抛出也没有打印日志,吞掉了序列化/反序列化阶段的潜在错误,会提升问题排查难度。
修复步骤
- 重写JSON转换器,明确指定转换的目标类型,不要用通用Object类型,同时禁止吞异常:
@Converter public class JpaJsonConverter implements AttributeConverter<LocalizedTexts, String> { private final static ObjectMapper objectMapper = new ObjectMapper(); @Override public String convertToDatabaseColumn(LocalizedTexts localizedTexts) { if (localizedTexts == null) { return null; } try { return objectMapper.writeValueAsString(localizedTexts); } catch (JsonProcessingException ex) { throw new IllegalArgumentException("LocalizedTexts转JSON失败", ex); } } @Override public LocalizedTexts convertToEntityAttribute(String localizedTextsJSON) { if (localizedTextsJSON == null || localizedTextsJSON.isBlank()) { return null; } try { return objectMapper.readValue(localizedTextsJSON, LocalizedTexts.class); } catch (IOException ex) { throw new IllegalArgumentException("JSON转LocalizedTexts失败", ex); } } }
- 调整Discount实体的Lombok注解,移除会生成final字段的
@Value,替换为JPA实体适用的注解组合,确保字段可被反射修改:
@Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder(builderMethodName = "internalBuilder") @Entity @Table(name="discount") public class Discount { // 原有字段、自定义builder逻辑保持不变,不要手动给字段加final修饰 @Id @Column(name = "ID", nullable = false, unique = true) @Type(type="uuid-char") UUID id; @Column(name="NO", nullable = false, unique = true) String no; @Column(name="I_NO", nullable = false, unique = true) Integer iNo; @Convert(converter = JpaJsonConverter.class) @Column(name="DESIGNATION", nullable = false) LocalizedTexts designation; @Convert(converter = JpaJsonConverter.class) @Column(name="PRINT_TEXT") LocalizedTexts printText; public static Discount.DiscountBuilder builder(UUID id, String no, LocalizedTexts designation) { return internalBuilder() .id(id) .no(no) .designation(designation); } }
如果希望实体尽量保持不可变,可以不用类级别的
@Setter,单独为需要Hibernate赋值的字段添加package级别访问权限的setter即可,核心要求是字段不能被final修饰。
- 校验枚举序列化配置:由于
LocalizedTexts的键是Language枚举,需要确保你使用的ObjectMapper可以正确序列化、反序列化枚举值,如果有自定义枚举序列化规则,提前注册到转换器使用的ObjectMapper实例中,避免反序列化后枚举键变成字符串类型引发后续业务错误。
内容的提问来源于stack exchange,提问作者thanili
相关产品推荐
相关产品推荐

