
本文介绍一种无需为每个嵌套结构重复编写自定义 Converter 的通用方式,利用 PostgreSQL 的 Json 类型配合 ObjectMapper 实现灵活、可复用的 JSON 反序列化,兼顾响应式编程特性与开发效率。
本文介绍一种无需为每个嵌套结构重复编写自定义 converter 的通用方式,利用 postgresql 的 `json` 类型配合 objectmapper 实现灵活、可复用的 json 反序列化,兼顾响应式编程特性与开发效率。
在 Spring Data R2DBC 中,原生不支持如 JPA 那样的深度对象图映射(例如 @Embedded 或嵌套实体自动展开),尤其当数据库字段存储的是 JSON 字符串(如 PostgreSQL 的 JSONB 类型)时,框架无法自动将其反序列化为 Java 嵌套对象。但通过合理分层设计与类型抽象,完全可以避免“为每个实体写一个 @ReadingConverter”的冗余模式。
✅ 推荐方案:io.r2dbc.postgresql.codec.Json + 函数式映射
核心思路是延迟解析、按需转换:不在 RowMapper 或 Converter 层硬编码目标类型,而是将原始 JSON 数据以类型安全的 Json 对象保留,再在 Service 或 DTO 转换层统一处理。
1. 实体类使用 Json 类型(PostgreSQL 专用)
@Table("my_table")
@Data
@Builder
public class MyObject {
@Id
private Integer id;
// 直接映射为 R2DBC PostgreSQL 提供的 Json 类型
private Json data; // 自动绑定 JSONB 字段,不触发提前解析
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
⚠️ 注意:
Json类型仅适用于r2dbc-postgresql。若使用其他数据库(如 H2、MySQL),需改用String并自行处理空值/转义。
jm-jsjkxyjs02-zlm-405下载使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
2. 定义灵活的 DTO 与嵌套模型
@Data
@Builder
public class MyObjectDTO {
private Integer id;
private YourNestedObjectType data; // 可任意替换为具体业务类
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
// 支持任意嵌套结构(含 List、Map、自定义对象)
public class YourNestedObjectType {
private String field1;
private List<otherobject> otherObjects;
private Map<string bigdecimal> metrics;
}</string></otherobject>
3. 统一映射工具(泛型 + 函数式)
避免重复 try-catch 和 ObjectMapper 实例化,封装为可复用方法:
@Component
public class JsonMapper {
private final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(new JavaTimeModule());
public <t> T fromJson(Json json, Class<t> targetType) {
if (json == null || json.isNull()) {
return null;
}
try {
return objectMapper.readValue(json.asString(), targetType);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(
"Failed to deserialize JSON field to " + targetType.getSimpleName(), e);
}
}
// 示例:转换为 DTO(推荐在 Service 层调用)
public MyObjectDTO toDto(MyObject entity) {
return MyObjectDTO.builder()
.id(entity.getId())
.data(fromJson(entity.getData(), YourNestedObjectType.class))
.createdAt(entity.getCreatedAt())
.updatedAt(entity.getUpdatedAt())
.build();
}
}</t></t>
4. 在 Repository 或 Service 中使用(响应式友好)
@Repository
public class MyObjectRepository {
private final Database database;
private final JsonMapper jsonMapper;
public MyObjectRepository(Database database, JsonMapper jsonMapper) {
this.database = database;
this.jsonMapper = jsonMapper;
}
public Flux<myobjectdto> findAllAsDto() {
return database
.execute("SELECT * FROM my_table")
.map(row -> {
MyObject entity = new MyObject();
entity.setId(row.get("id", Integer.class));
entity.setData(row.get("data", Json.class)); // ← 关键:保持 Json 类型
entity.setCreatedAt(row.get("created_at", LocalDateTime.class));
entity.setUpdatedAt(row.get("updated_at", LocalDateTime.class));
return entity;
})
.map(jsonMapper::toDto); // ← 统一转换入口
}
}</myobjectdto>
? 优势总结
- ✅ 真正通用:
fromJson(Json, Class方法可复用于任意嵌套类型,无需新增 Converter;) - ✅ 解耦清晰:数据访问层(Entity)与领域模型(DTO/VO)分离,符合响应式分层架构;
- ✅ 错误可控:JSON 解析异常在业务层捕获并转化为语义明确的
IllegalArgumentException,便于监控与重试; - ✅ 性能合理:仅在必要时解析(非每行都触发),且
Json.asString()是轻量操作; - ✅ 兼容扩展:未来若需支持 MongoDB 或其它 JSON 存储,只需替换
Json抽象层。
⚠️ 注意事项
- 若使用非 PostgreSQL 数据库,请将
private Json data替换为private String data,并在fromJson中增加null/空字符串校验; -
ObjectMapper应单例复用(如上例中@Component管理),避免频繁创建影响性能; - 对高频调用场景,可考虑对常用类型(如
YourNestedObjectType)做TypeReference缓存,进一步优化反射开销; - 不建议在
@ReadingConverter中直接调用ObjectMapper—— 它会破坏 R2DBC 的类型推导链,且难以泛型化。
通过该方案,你既保住了 R2DBC 的非阻塞优势,又获得了接近 JPA 的开发体验:一次封装,处处复用,嵌套无忧。











