如何使用 Jooq 获取多个级别的一对多嵌套
Posted
技术标签:
【中文标题】如何使用 Jooq 获取多个级别的一对多嵌套【英文标题】:How to Fetch Multiple Level One to many Nesting with Jooq 【发布时间】:2021-07-27 18:00:40 【问题描述】:这是场景,我有 5 个表之间存在一对多关系,我必须在下面给定的 Pojos 和 Jooq 中以分层方式映射结果数据。
数据库表是 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)
);
Then I got this post 并按照下面给出的方法和帖子中给出的其他 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)
)));
【问题讨论】:
您最终需要 POJO 还是 JSON 输出?您使用的是什么数据库产品? @LukasEder 我需要首先将响应映射到 POJO,最后 API 端点应该使用 JSON 响应。我正在使用 PostgreSQL 数据库 【参考方案1】:
JOIN
方法
从历史上看,大多数 ORM 都试图以某种方式使用连接来嵌套集合,因为连接是 SQL 中唯一被广泛支持的“连接”集合(但不嵌套)的方式。结果是一个扁平的、非规范化的表,很难再次规范化。有很多重复项,甚至可能是不需要的笛卡尔积,甚至可能无法确定哪个嵌套集合属于父值。在您的情况下,这是可能的,但在服务器和客户端上都非常浪费。 A
的值会重复很多次。
已经实施了一些变通办法,包括第三方(针对 jOOQ)。替代方法包括运行多个查询并在之后连接这些值。所有这些都非常乏味。
幸运的是,jOOQ 3.14+ 支持开箱即用的嵌套集合!
使用 SQL/JSON 的 jOOQ 3.14 方法
The jOOQ 3.14 appraoch to nesting collections is using 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 是关于 dynamic SQL,很有可能,您可以使用动态 SQL 自动执行一些嵌套。
以上所有内容也适用于 PostgreSQL 中的 JSONB
,只需使用 jsonbArrayAgg()
和 jsonbObject()
代替。
请注意,JSON_ARRAYAGG()
将空集聚合到 NULL
,而不是空集 []
。 If that's a problem, use COALESCE()
将以上内容映射到 POJO 中
如果您的类路径中有 Jackson 或 Gson,您现在可以在末尾写 fetchInto(APojo.class)
来映射生成的 JSON 树。但是您可能只是要使用 Jackson 或 Gson 再次将 POJO 映射回 JSON,所以从高层次上看,我认为您不会从这一步中获得很多价值。
嵌套集合的 jOOQ 3.15 方法
从 jOOQ 3.15 开始,将实现对类型安全映射和嵌套集合的多项改进
#3884MULTISET
和 ARRAY
子查询支持的构造函数(终于!)
#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)
在这种情况下,您将能够编写如下内容:
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 的未来版本可能会提供更方便的语法,@ 987654331@.
【讨论】:
感谢@LukasEder 的快速响应,我只是想避免嵌套的选择查询。 @Hrishi:你为什么要避开它们? 我认为加入表比在select
中嵌套子查询作为fields
来获取数据更优化,因为select
中的子查询作为fields
执行的次数与否一样多.父查询获取的记录数。
@Hrishi:嗯,你打算以分层形式从数据库中转储整个数据集吗?在这种情况下,是的,哈希连接(O(N)
)将优于嵌套聚合(O(A * log B * log C * log D * log E)
最好是多项式,即O(N^5)
最坏情况,尽管不太可能)。它可能仍然可以,具体取决于数据集的大小,尤其是取决于每个嵌套集合的子集大小。但是,如果您只为一个小数据集获取此数据(例如,在*** A
上有一些过滤器,那么您会没事的(无论如何您都会得到嵌套循环连接)。
正确,对于小型数据集,它可以正常工作。我不会转储整个数据集。它将在前 2 级获取单个记录,但从第 3 级开始,第 3 级本身可能是 50-100,并且在第 4 和第 5 级对于第 3 级的每条记录相同。感谢您的解决方案!以上是关于如何使用 Jooq 获取多个级别的一对多嵌套的主要内容,如果未能解决你的问题,请参考以下文章