Spring Boot Java API存储JSON数组至PostgreSQL jsonb列报错
问题场景
请求对象与POJO包含JSON数组字段,需要存入PostgreSQL的jsonb类型列,请求示例如下:
{ "productId": "1", "product": [ { "name": "PENCIL", "quantity": "5" } ] }
通过Postman调用接口时触发异常,异常信息翻译后如下:
类型定义错误: [简单类型,类 io.r2dbc.postgresql.codec.Json]; 嵌套异常为 com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 无法构造
io.r2dbc.postgresql.codec.Json的实例(不存在默认构造器等创建器): 抽象类型需要映射到具体类型、使用自定义反序列化器,或包含额外类型信息
来源: (io.netty.buffer.ByteBufInputStream);
异常原因
io.r2dbc.postgresql.codec.Json是抽象类,Jackson默认无法实例化抽象类——因为抽象类没有可直接调用的构造器(比如无参构造),导致接口接收请求时,Jackson无法将JSON字符串反序列化为该抽象类型的对象。
解决方案
方案1:POJO改用具体数据类型(推荐)
不在POJO中直接使用Json抽象类,改用List<自定义对象>或String类型,存入数据库时再转换为Json实例。
示例代码
- 定义POJO和子对象:
// 请求POJO public class ProductRequest { private String productId; private List<ProductItem> product; // 用List替代Json抽象类 // Getter、Setter } // 子对象 public class ProductItem { private String name; private String quantity; // Getter、Setter }
- 保存时转换为
Json存入数据库:
@Autowired private ObjectMapper objectMapper; public Mono<Void> saveProduct(ProductRequest request) { try { // 将List转为JSON字符串,再创建Json实例 String productJsonStr = objectMapper.writeValueAsString(request.getProduct()); Json productJson = Json.create(productJsonStr); // 构建数据库实体并保存 ProductEntity entity = new ProductEntity(request.getProductId(), productJson); return productRepository.save(entity).then(); } catch (JsonProcessingException e) { return Mono.error(new IllegalArgumentException("JSON转换失败", e)); } }
方案2:自定义Jackson序列化/反序列化器
如果必须在POJO中使用Json类型,可为其编写自定义的序列化/反序列化逻辑,让Jackson知道如何处理该抽象类。
示例代码
- 编写序列化器:
public class JsonSerializer extends StdSerializer<Json> { public JsonSerializer() { super(Json.class); } @Override public void serialize(Json value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 将Json对象转为原始JSON字符串输出 gen.writeRawValue(value.asString()); } }
- 编写反序列化器:
public class JsonDeserializer extends StdDeserializer<Json> { public JsonDeserializer() { super(Json.class); } @Override public Json deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // 读取JSON字符串并创建Json实例 String jsonStr = p.readValueAsTree().toString(); return Json.create(jsonStr); } }
- 在POJO字段上添加注解:
public class ProductRequest { private String productId; @JsonSerialize(using = JsonSerializer.class) @JsonDeserialize(using = JsonDeserializer.class) private Json product; // Getter、Setter }
方案3:注册Spring Data R2DBC自定义转换器
通过Spring的类型转换器,自动完成List/自定义对象与Json类型的转换,无需在业务代码中手动处理。
示例代码
@Configuration public class R2dbcConfig { @Bean public R2dbcCustomConversions r2dbcCustomConversions(ObjectMapper objectMapper) { List<Converter<?, ?>> converters = new ArrayList<>(); // 自定义:List转Json converters.add(new Converter<List<?>, Json>() { @Override public Json convert(List<?> source) { try { return Json.create(objectMapper.writeValueAsString(source)); } catch (JsonProcessingException e) { throw new IllegalArgumentException("List转JSON失败", e); } } }); // 自定义:Json转List<ProductItem> converters.add(new Converter<Json, List<ProductItem>>() { @Override public List<ProductItem> convert(Json source) { try { return objectMapper.readValue( source.asString(), new TypeReference<List<ProductItem>>() {} ); } catch (JsonProcessingException e) { throw new IllegalArgumentException("JSON转List失败", e); } } }); return R2dbcCustomConversions.of(PostgresDialect.INSTANCE, converters); } }
配置完成后,POJO可直接使用List<ProductItem>类型,Spring会自动在数据库交互时完成与Json的转换。
内容的提问来源于stack exchange,提问作者developer

