简体   繁体   English

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

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

Here is the Scenario, I have 5 tables having one to many relations with each other, I have to map result data in hierarchical manner in below given Pojos with Jooq.这是场景,我有 5 个相互之间有一对多关系的表,我必须在下面给定 Pojos 和 Jooq 中以分层方式 map 结果数据。

DB Tables are a, b, c, d, e 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;

}

Expected sample response预期的样本响应

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

I tried something like this first but It did not work because fetch groups only accepts 2 arguments我首先尝试了类似的方法,但它不起作用,因为提取组只接受 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)
     ); 

Then I got this post and tried it as given below and other 2 method given in the post, but this also did not worked because Collectors.toMap accepts only 2 arguments and I have to fetch 5 level hierarchical data. 然后我得到了这篇文章,并尝试了下面给出的其他 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)
 )));

The JOIN approach JOIN方法

Historically, most ORMs attempted to nest collections in some way using joins, since joins have been the only widely supported way of "connecting" collections (but not nesting them) in SQL.从历史上看,大多数 ORM 尝试使用连接以某种方式嵌套 collections,因为连接是唯一广泛支持的在 Z9778840A0100CB30C982Z876741B0BA 中“连接”collections(但不嵌套它们)的方式。 The result is a flat, denormalised table, that is hard to normalise again.结果是一个扁平的、非规范化的表,很难再次规范化。 There are a lot of duplicates, and possibly even unwanted cartesian products, and it might not even be possible to be sure what nested collection belongs to a parent value.有很多重复项,甚至可能是不需要的笛卡尔积,甚至可能无法确定哪个嵌套集合属于父值。 In your case, it would be possible, but very wasteful both on the server and on the client.在您的情况下,这是可能的,但在服务器和客户端上都非常浪费。 The values of A would be repeated many many times. A的值会重复很多次。

Some workarounds have been implemented, including by third parties (for jOOQ).一些变通方法已经实现,包括第三方(对于 jOOQ)。 Alternatives include running several queries and connecting the values afterwards.替代方法包括运行多个查询并在之后连接这些值。 All of them very tedious.所有这些都非常乏味。

Luckily, jOOQ 3.14+ offers support for nesting collections out of the box!幸运的是,jOOQ 3.14+ 提供了对嵌套 collections 开箱即用的支持!

A jOOQ 3.14 approach using SQL/JSON使用 SQL/JSON 的 jOOQ 3.14 方法

The jOOQ 3.14 appraoch to nesting collections is using SQL/JSON behind the scenes (or SQL/XML, but in your case, JSON seems more appropriate). 嵌套 collections 的 jOOQ 3.14 方法在幕后使用 SQL/JSON (或 SQL/XML,但在您的情况下,JSON 似乎更合适)。

From your question, I don't see why you need the POJO intermediate step, so perhaps you can bypass that and generate the JSON directly in the database.从您的问题来看,我不明白您为什么需要 POJO 中间步骤,所以也许您可以绕过它并直接在数据库中生成 JSON 。 If not, see below.如果没有,请参见下文。

Write this query:编写此查询:

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();

The usual static import is assumed:假设通常的 static 导入:

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

Since jOOQ is all about dynamic SQL , chances are, you can automate some of the nesting with dynamic SQL.由于 jOOQ 完全是关于动态 SQL ,因此您可以使用动态 SQL 自动化一些嵌套。

All of the above also works with JSONB in PostgreSQL, just use jsonbArrayAgg() and jsonbObject() instead.以上所有方法也适用于JSONB中的 JSONB,只需使用jsonbArrayAgg()jsonbObject()代替。

Note that JSON_ARRAYAGG() aggregates empty sets into NULL , not into an empty [] .请注意, JSON_ARRAYAGG()将空集聚合到NULL中,而不是空[]中。 If that's a problem, use COALESCE()如果这是一个问题,请使用COALESCE()

Mapping the above into POJOs将以上内容映射到 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. 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. 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.

The jOOQ 3.15 approach to nesting collections嵌套 collections 的 jOOQ 3.15 方法

Starting from jOOQ 3.15, a number of improvements to type safe mapping and nesting collections will be implemented从 jOOQ 3.15 开始,将实现对类型安全映射和嵌套 collections 的多项改进

  • #3884 MULTISET and ARRAY constructor from subquery support (finally!) #3884来自子查询支持的MULTISETARRAY构造函数(终于!)
  • #7100 Ad-hoc Field data type conversion convenience #7100 Ad-hoc Field数据类型转换方便
  • #11804 Type safe mapping of Record[N] types to constructor references #11804 Record[N] 类型到构造函数引用的类型安全映射
  • #11812 Support for nested ROW expressions in projections #11812支持投影中的嵌套ROW表达式

With all of the above, in case you really need your POJO intermediary step (and assuming you will have the necessary "immutable constructors" on your POJOs, eg like canonical record constructors in Java 16+).综上所述,如果您真的需要POJO 中间步骤(并假设您的 POJO 上有必要的“不可变构造函数”,例如 Java 16+ 中的规范记录构造函数)。 Eg例如

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) {}

In that case, you will be able to write something like this (exact API still a moving target at this point):在这种情况下,您将能够编写如下内容(确切的 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));

If you prefer List<SomePojo> over SomePojo[] , then you'd just have to use the new ad-hoc conversion from arrays to list on the array expressions, eg如果您更喜欢List<SomePojo>而不是SomePojo[] ,那么您只需要使用来自 arrays 的新临时转换来列出数组表达式,例如

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

I'll update this part of the answer, once the API has stabilised.一旦 API 稳定下来,我将更新这部分答案。

Further outlook进一步 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 join syntax for ordinary to-one joins . 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