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

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值,直接抛出异常。
  • 额外问题:你在转换器中捕获异常后没有抛出也没有打印日志,吞掉了序列化/反序列化阶段的潜在错误,会提升问题排查难度。
修复步骤
  1. 重写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);
        }
    }
}
  1. 调整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修饰。

  1. 校验枚举序列化配置:由于LocalizedTexts的键是Language枚举,需要确保你使用的ObjectMapper可以正确序列化、反序列化枚举值,如果有自定义枚举序列化规则,提前注册到转换器使用的ObjectMapper实例中,避免反序列化后枚举键变成字符串类型引发后续业务错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 23:39:36