如何泛化JPA的JsonConverter实现以消除代码重复?
泛化JSON转换器以消除代码重复
你的场景中多个AttributeConverter仅泛型类型不同,逻辑完全重复,可以通过泛型抽象类提取通用逻辑,让具体转换器仅需指定目标类型即可。
方案1:基础泛型抽象类(无Spring依赖)
首先创建通用的抽象转换器,封装所有重复的序列化/反序列化逻辑:
@Slf4j public abstract class GenericJsonConverter<T> implements AttributeConverter<T, String> { private final ObjectMapper objectMapper; private final Class<T> targetClass; // 构造方法传入目标类型的Class对象 protected GenericJsonConverter(Class<T> targetClass) { this.targetClass = targetClass; this.objectMapper = new ObjectMapper(); // 仅初始化一次JavaTimeModule,避免重复注册 objectMapper.registerModule(new JavaTimeModule()); } @Override public String convertToDatabaseColumn(T attribute) { if (attribute == null) { return null; } try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException ex) { log.error("无法将对象 {} 序列化为JSON", attribute, ex); throw new InternalServerErrorException("无法将对象转换为JSON字符串", ex); } } @Override public T convertToEntityAttribute(String dbData) { if (dbData == null) { return null; } try { return objectMapper.readValue(dbData, targetClass); } catch (JsonProcessingException ex) { log.error("无法将JSON字符串 {} 反序列化为对象", dbData, ex); throw new InternalServerErrorException("无法将JSON字符串转换为对象", ex); } } }
然后,每个具体的转换器只需继承该抽象类,指定泛型并传入目标类:
// IndUserInfo对应的转换器 @Converter(autoApply = true) public class IndUserInfoJsonConverter extends GenericJsonConverter<IndUserInfo> { public IndUserInfoJsonConverter() { super(IndUserInfo.class); } } // UserInfo对应的转换器 @Converter(autoApply = true) public class UserInfoJsonConverter extends GenericJsonConverter<UserInfo> { public UserInfoJsonConverter() { super(UserInfo.class); } } // 其他如DuplicateInfo、PartnershipInfo等转换器依此类推
方案2:Spring环境下复用全局ObjectMapper
如果你的项目基于Spring,建议复用容器中配置好的ObjectMapper(比如已经配置了JavaTimeModule、自定义序列化规则等),避免重复创建实例:
@Slf4j public abstract class GenericJsonConverter<T> implements AttributeConverter<T, String> { private final ObjectMapper objectMapper; private final Class<T> targetClass; // 注入Spring容器中的全局ObjectMapper protected GenericJsonConverter(ObjectMapper objectMapper, Class<T> targetClass) { this.objectMapper = objectMapper; this.targetClass = targetClass; } // 序列化、反序列化方法同方案1 @Override public String convertToDatabaseColumn(T attribute) { if (attribute == null) { return null; } try { return objectMapper.writeValueAsString(attribute); } catch (JsonProcessingException ex) { log.error("无法将对象 {} 序列化为JSON", attribute, ex); throw new InternalServerErrorException("无法将对象转换为JSON字符串", ex); } } @Override public T convertToEntityAttribute(String dbData) { if (dbData == null) { return null; } try { return objectMapper.readValue(dbData, targetClass); } catch (JsonProcessingException ex) { log.error("无法将JSON字符串 {} 反序列化为对象", dbData, ex); throw new InternalServerErrorException("无法将JSON字符串转换为对象", ex); } } }
具体子类通过@Autowired注入ObjectMapper:
@Converter(autoApply = true) public class IndUserInfoJsonConverter extends GenericJsonConverter<IndUserInfo> { @Autowired public IndUserInfoJsonConverter(ObjectMapper objectMapper) { super(objectMapper, IndUserInfo.class); } }
最终使用
实体类中的@Convert注解无需修改,继续使用对应的具体转换器即可:
@Entity @Table(name = "orders") @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Order { @Convert(converter = UserInfoJsonConverter.class) private UserInfo userInfo; @Convert(converter = IndUserInfoJsonConverter.class) private IndUserInfo indUserInfo; // 其他字段保持不变 }
核心优化点
- 所有重复的序列化/反序列化逻辑集中在抽象类,仅需维护一次
- ObjectMapper仅初始化/配置一次,避免重复注册模块带来的性能损耗
- 具体转换器代码极简,仅需指定目标类型
内容的提问来源于stack exchange,提问作者ElbrusGarayev
相关产品推荐
相关产品推荐

