簡體   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