17370845950

JOOQ 中使用 Row.mapping 实现子查询字段到嵌套 DTO 的映射

本文介绍如何在 jooq 中通过 `row2.mapping()` 对关联表字段进行内联映射,将一对多/多对一关系中的子实体(如 parent)直接构造为 java record 或 dto,避免手动组装或多次查询。

在使用 JOOQ 进行关系型数据查询时,常需将主表(如 child)与关联表(如 parent)的字段一并查出,并映射为包含嵌套对象的 Java 结构(例如 ChildDTO 内含 ParentDTO 字段)。JOOQ 并不原生支持“自动嵌套实体映射”,但可通过其强大的 Row.mapping() 机制优雅实现——关键在于正确使用导航路径与类型化 Row 表达式

以下是一个典型且推荐的实现方式(基于你提供的 schema):

List result = dslContext
    .select(
        CHILD.ID,
        CHILD.NAME,
        row(CHILD.parent().ID, CHILD.parent().NAME)
            .mapping(ParentDTO::new)  // ✅ 类型安全:Row2 → ParentDTO
    )
    .from(

CHILD) .fetch(Records.mapping(ChildDTO::new));

其中:

  • CHILD.parent() 是 JOOQ 自动生成的外键导航路径(需确保代码生成时启用了 配置);
  • row(...).mapping(...) 返回一个 SelectField,可直接参与顶层 select();
  • Records.mapping(ChildDTO::new) 要求 ChildDTO 构造函数按 SELECT 字段顺序接收参数:(Long childId, String childName, ParentDTO parent)。

对应的 DTO 定义示例:

public record ChildDTO(Long id, String name, ParentDTO parent) {}
public record ParentDTO(Long id, String name) {}

⚠️ 注意事项:

  • 不要对 row(...) 包裹 DSL.field(...) —— 这会丢失泛型信息,导致 mapping() 不可用;
  • row(...).mapping(...) 仅适用于固定字段数的组合(如 row(a,b) → Row2,row(a,b,c) → Row3),JOOQ 提供了从 Row2 到 Row8 的完整支持;
  • 若需更灵活的映射(如动态字段、空值处理),可改用 convertFrom() + 自定义 Converter
  • 此方案本质是单次 SQL JOIN 查询 + 内存中结构重组,性能优于 N+1 查询,也比手动 fetchInto() + 循环组装更简洁安全。

总结:通过 RowN.mapping(),你无需引入额外框架(如 MapStruct 或 Jackson),即可在 JOOQ 的类型安全体系内完成“扁平结果集 → 嵌套对象”的声明式映射,是构建响应式、层次化 DTO 的最佳实践之一。