繁体   English   中英

如何使用 Jooq 获取多个级别的一对多嵌套

[英]How to Fetch Multiple Level One to many Nesting with Jooq

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

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"
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

我首先尝试了类似的方法,但它不起作用,因为提取组只接受 2 arguments

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 个 arguments 并且我必须获取 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)
 )));

JOIN方法

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

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

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

使用 SQL/JSON 的 jOOQ 3.14 方法

嵌套 collections 的 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();

假设通常的 static 导入:

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

由于 jOOQ 完全是关于动态 SQL ,因此您可以使用动态 SQL 自动化一些嵌套。

以上所有方法也适用于JSONB中的 JSONB,只需使用jsonbArrayAgg()jsonbObject()代替。

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

将以上内容映射到 POJO

If you have Jackson or Gson on your classpath, you can now just write fetchInto(APojo.class) at the end to map the resulting JSON tree. But you're probably just going to map the POJOs back into JSON again using either Jackson or Gson, so from a high level, I don't think you get a lot of value out of this step.

嵌套 collections 的 jOOQ 3.15 方法

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

  • #3884来自子查询支持的MULTISETARRAY构造函数(终于!)
  • #7100 Ad-hoc Field数据类型转换方便
  • #11804 Record[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) {}

在这种情况下,您将能够编写如下内容(确切的 API 在这一点上仍然是一个移动目标):

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[] ,那么您只需要使用来自 arrays 的新临时转换来列出数组表达式,例如

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

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

进一步 outlook

Since these types of "nested collection joins" will become very common in jOOQ, irrespective of whether SQL collections are nested in SQL, or JSON collections, or XML collections, future versions of jOOQ will probably offer a more convenient syntax, similar to the implicit普通一对一连接的连接语法

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM