4

这是场景,我有 5 个相互之间有一对多关系的表,我必须在下面给定 Pojos 和 Jooq 中以分层方式映射结果数据。

DB表是a,b,c,d,e

// Here are response Pojo's
Class APojo {
  public string name;
  public List<BPojo> listOfB;

}

Class BPojo {
  public string name;
  public List<CPojo> listOfC;
}

Class CPojo {
  public string name;
  public List<DPojo> listOfD;

}

Class DPojo {
  public string name;
  public List<EPojo> listOfE;

}

Class EPojo {
  public string name;

}

预期的样本响应

{
  "name":"A1",
  "list_of_b":[
    {
      "name":"A1B1",
      "list_of_c":[
        {
          "name":"A1B1C1",
          "list_of_d":[
            {
              "name":"A1B1C1D1",
              "list_of_e":[
                {
                  "name":"A1B1C1D1E1"
                },
                {
                  "name":"A1B1C1D1E2"
                }
              ]
            },
            {
              "name":"A1B1C1D2",
              "list_of_e":[
                {
                  "name":"A1B1C1D2E1"
                },
                {
                  "name":"A1B1C1D2E2"
                }
              ]
            }
          ]
        },
        {
          "name":"A1B1C2",
          "list_of_d":[
            {
              "name":"A1B1C2D1",
              "list_of_e":[
                {
                  "name":"A1B1C2D1E1"
                },
                {
                  "name":"A1B1C2D1E2"
                }
              ]
            },
            {
              "name":"A1B1C2D2",
              "list_of_e":[
                {
                  "name":"A1B1C2D2E1"
                },
                {
                  "name":"A1B1C2D2E2"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name":"A1B2",
      "list_of_c":[
        {
          "name":"A1B2C1",
          "list_of_d":[
            {
              "name":"A1B2C1D1",
              "list_of_e":[
                {
                  "name":"A1B1C1D1"
                },
                {
                  "name":"A1B1C1D2"
                }
              ]
            },
            {
              "name":"A1B2C1D2",
              "list_of_e":[
                {
                  "name":"A1B1C1D1"
                },
                {
                  "name":"A1B1C1D2"
                }
              ]
            }
          ]
        },
        {
          "name":"A1B2C2",
          "list_of_d":[
            {
              "name":"A1B2C2D1",
              "list_of_e":[
                {
                  "name":"A1B1C1D1"
                },
                {
                  "name":"A1B1C1D2"
                }
              ]
            },
            {
              "name":"A1B2C2D2",
              "list_of_e":[
                {
                  "name":"A1B1C1D1"
                },
                {
                  "name":"A1B1C1D2"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

我首先尝试过这样的事情,但它没有用,因为 fetch groups 只接受 2 个参数

using(configuration()).select(A.fields())
    .select(B.fields())
    .select(C.fields())
    .select(D.fields())
    .select(E.fields())
    .from(A)
    .join(B).on(A.ID.eq(B.A_ID)
    .join(C).on(B.ID.eq(C.B_ID)
    .join(D).on(C.ID.eq(D.C_ID)
    .join(E).on(D.ID.eq(E.D_ID)
    .fetchGroups(
        r -> r.into(A).into(APojo.class),
        r -> r.into(B).into(BPojo.class),
        r -> r.into(C).into(CPojo.class),
        r -> r.into(D).into(DPojo.class),
        r -> r.into(E).into(EPojo.class)
     ); 

然后我得到了这篇文章,并按照下面给出的方法进行了尝试,并尝试了这篇文章中给出的其他 2 种方法,但这也没有奏效,因为Collectors.toMap只接受 2 个参数,我必须获取 5 级分层数据。

using(configuration()).select(A.fields())
        .select(B.fields())
        .select(C.fields())
        .select(D.fields())
        .select(E.fields())
        .from(A)
        .join(B).on(A.ID.eq(B.A_ID)
        .join(C).on(B.ID.eq(C.B_ID)
        .join(D).on(C.ID.eq(D.C_ID)
        .join(E).on(D.ID.eq(E.D_ID)
        .collect(Collectors.groupingBy(
            r -> r.into(A).into(APojo.class),
            Collectors.toMap(
                r -> r.into(B).into(BPojo.class),
                r -> r.into(C).into(CPojo.class)
                r -> r.into(D).into(DPojo.class)
                r -> r.into(E).into(EPojo.class)
 )));
4

1 回答 1

2

JOIN方法_

从历史上看,大多数 ORM 尝试使用连接以某种方式嵌套集合,因为连接是 SQL 中唯一广泛支持的“连接”集合(但不嵌套)的方式。结果是一个扁平的、非规范化的表,很难再次规范化。有很多重复项,甚至可能是不需要的笛卡尔积,甚至可能无法确定哪个嵌套集合属于父值。在您的情况下,这是可能的,但在服务器和客户端上都非常浪费。的值A会重复很多次。

一些变通方法已经实现,包括第三方(对于 jOOQ)。替代方法包括运行多个查询并在之后连接这些值。所有这些都非常乏味。

幸运的是,jOOQ 3.14+ 提供了开箱即用的嵌套集合支持!

使用 SQL/JSON 的 jOOQ 3.14 方法

jOOQ 3.14 嵌套集合的方法是在幕后使用 SQL/JSON(或 SQL/XML,但在您的情况下,JSON 似乎更合适)。

从您的问题来看,我不明白您为什么需要 POJO 中间步骤,所以也许您可以绕过它并直接在数据库中生成 JSON。如果没有,请参见下文。

编写此查询:

ctx
  .select(
    // Optionally, wrap this level in jsonArrayAgg(jsonObject()) too, like the others
    A.NAME,
    field(
      select(jsonArrayAgg(jsonObject(
        key("name").value(B.NAME),
        key("list_of_c").value(
          select(jsonArrayAgg(jsonObject(
            key("name").value(C.NAME),
            key("list_of_d").value(
              select(jsonArrayAgg(jsonObject(
                key("name").value(D.NAME),
                key("list_of_e").value(
                  select(jsonArrayAgg(jsonObject(key("name").value(E.NAME))))
                  .from(E)
                  .where(E.D_ID.eq(D.ID))
                )
              )))
              .from(D)
              .where(D.C_ID.eq(C.ID))
            )
          )))
          .from(C)
          .where(C.B_ID.eq(B.ID))
        )
      )))
      .from(B)
      .where(B.A_ID.eq(A.ID))
    ).as("list_of_b")
  )
  .from(A)
  .fetch();

假设通常的静态导入:

import static ord.jooq.impl.DSL.*;

由于 jOOQ 都是关于动态 SQL的,因此很有可能,您可以使用动态 SQL 自动化一些嵌套。

以上所有内容也适用JSONB于 PostgreSQL,只需使用jsonbArrayAgg()andjsonbObject()代替。

请注意,JSON_ARRAYAGG()将空集聚合成NULL,而不是空集[]如果这是一个问题,请使用COALESCE()

将以上内容映射到 POJO

如果您的类路径中有 Jackson 或 Gson,您现在可以fetchInto(APojo.class)在最后编写以映射生成的 JSON 树。但是您可能只是要使用 Jackson 或 Gson 再次将 POJO 映射回 JSON,因此从高层次上看,我认为您不会从这一步中获得很多价值。

jOOQ 3.15 嵌套集合的方法

从 jOOQ 3.15 开始,将实现对类型安全映射和嵌套集合的许多改进

  • #3884 MULTISETARRAY子查询支持的构造函数(终于!)
  • #7100 Ad-hocField数据类型转换方便
  • #11804Record[N]类型到构造函数引用的类型安全映射
  • #11812支持ROW投影中的嵌套表达式

有了以上所有内容,如果您真的需要POJO 中间步骤(并假设您的 POJO 上有必要的“不可变构造函数”,例如 Java 16+ 中的规范记录构造函数)。例如

record EPojo (String name) {}
record DPojo (String name, EPojo[] listOfE) {}
record CPojo (String name, DPojo[] listOfD) {}
record BPojo (String name, CPojo[] listOfC) {}
record APojo (String name, BPojo[] listOfB) {}

在这种情况下,您将能够编写如下内容:

ctx
  .select(A.NAME, array(
     select(row(B.NAME, array(
       select(row(C.NAME, array(
         select(row(D.NAME, array(
           select(row(E.NAME).mapping(EPojo::new))
           .from(E)
           .where(E.D_ID.eq(D.ID))
         )).mapping(DPojo::new))
         .from(D)
         .where(D.C_ID.eq(C.ID))
       )).mapping(CPojo::new))
       .from(C)
       .where(C.B_ID.eq(B.ID))
     )).mapping(BPojo::new))
     .from(B)
     .where(B.A_ID.eq(A.ID))
  )
  .from(A)
  .fetch(Records.mapping(APojo::new));

如果您更喜欢List<SomePojo>SomePojo[]那么您只需要使用新的从数组的临时转换来列出数组表达式,例如

array(select(...)).convertFrom(Arrays::asList)

一旦 API 稳定下来,我将更新这部分答案。

进一步展望

由于这些类型的“嵌套集合连接”将在 jOOQ 中变得非常普遍,无论 SQL 集合是嵌套在 SQL、JSON 集合还是 XML 集合中,jOOQ 的未来版本可能会提供更方便的语法,类似于隐式普通一对一连接的连接语法

于 2021-05-06T06:53:02.597 回答