英文:
JOOQ fetch over foreign keys table
问题
我有三个表:
用户(Users)
密钥(Keys)
用户密钥(UserKeys)
用户密钥(UserKeys)
表具有来自 用户(Users)
和 密钥(Keys)
表的主键,以建立用户与密钥之间的关系。
如何获取一个带有其所有相关密钥的 用户(User)
?
如果存在其他附加表(例如 用户角色(UserRoles)
),等等。一般来说,如何检索用户和所有通过外键表关联的关联行?
英文:
I have three tables:
Users
Keys
UserKeys
The UserKeys
table has both primary keys from Users
and Keys
tables to establish the relation between users and keys.
How to fetch a User
with all it's related keys?
What if additional tables exist (for instance UserRoles
), etc. In general, how to fetch a user and all associated rows, related via foreign keys tables?
答案1
得分: 1
使用标准 SQL 的 JOIN
我假设你正在使用 jOOQ 的 代码生成器。你可以像在 SQL 中写联接一样来写:
ctx.select() // 可选择在这里列出列,明确指定
.from(USERS)
.join(USER_KEYS).on(USERS.ID.eq(USER_KEYS.USER_ID))
.join(KEYS).on(USER_KEYS.KEY_ID.eq(KEYS.ID))
.where(USERS.NAME.eq("something"))
.fetch();
嵌套集合
> 如果存在其他表(例如 UserRoles 等),一般情况下如何获取用户和所有通过外键表关联的关联行?
我不确定这是否仍然是同一个问题。上面的内容可能是关于如何进行一般联接的,而这个问题似乎更具体,关于如何获取嵌套集合?
因为 JOIN
总会产生笛卡尔积,一旦你联接多个到多个路径,这是不可取的。从 jOOQ 3.14 开始,如果你的数据库支持,你可以使用 SQL/XML 或 SQL/JSON 作为解决方法。从 jOOQ 3.15 开始,你可以使用 MULTISET
。例如,JSON 解决方案可能如下所示:
List<User> users =
ctx.select(jsonObject(
jsonEntry("id", USERS.ID),
jsonEntry("name", USERS.NAME),
jsonEntry("keys", field(
select(jsonArrayAgg(jsonObject(KEYS.NAME, KEYS.ID)))
.from(KEYS)
.join(USER_KEYS).on(KEYS.ID.eq(USER_KEYS.KEY_ID))
.where(USER_KEYS.USER_ID.eq(USER.ID))
)),
jsonEntry("roles", field(
select(jsonArrayAgg(jsonObject(ROLES.NAME, ROLES.ID)))
.from(ROLES)
.join(USER_ROLES).on(ROLES.ID.eq(USER_ROLES.ROLE_ID))
.where(USER_ROLES.USER_ID.eq(USER.ID))
))
))
.from(USERS)
.where(USERS.NAME.eq("something"))
.fetchInto(User.class);
假设 User
类看起来像这样,并且你的类路径上有 Gson 或 Jackson 用于将 JSON 映射到 Java 数据结构:
class Key {
long id;
String name;
}
class Role {
long id;
String name;
}
class User {
long id;
String name;
List<Key> keys;
List<Role> roles;
}
当然,你可以直接映射到 Java 数据结构并直接产生 JSON 结果,无需进一步映射。此博文还提供更多细节,或者这篇博文解释了如何使用 MULTISET
。
请注意,JSON_ARRAYAGG()
会将空集合聚合为 NULL
,而不是空的 []
。如果这是个问题,可以使用 COALESCE()
。
使用 MULTISET
的解决方案如下所示:
List<User> users =
ctx.select(
USERS.ID,
USERS.NAME,
multiset(
select(KEYS.NAME, KEYS.ID)
.from(KEYS)
.join(USER_KEYS).on(KEYS.ID.eq(USER_KEYS.KEY_ID))
.where(USER_KEYS.USER_ID.eq(USER.ID))
).as("keys").convertFrom(r -> r.map(Records.mapping(Key::new))),
multiset(
select(ROLES.NAME, ROLES.ID)
.from(ROLES)
.join(USER_ROLES).on(ROLES.ID.eq(USER_ROLES.ROLE_ID))
.where(USER_ROLES.USER_ID.eq(USER.ID))
).as("roles").convertFrom(r -> r.map(Records.mapping(Role::new)))
)
.from(USERS)
.where(USERS.NAME.eq("something"))
.fetch(Records.mapping(User::new));
上述方法使用了各种 Records.mapping()
的重载,以及临时数据类型转换,假定存在一个不可变的构造函数,就像如果你的类是 Java 16 的 record 一样:
record Key (int id, String name) {}
record Role (int id, String name) {}
record User (int id, String name, List<Key> keys, List<Role> roles) {}
使用多个查询
如果你不能使用上述方法,因为你无法使用 jOOQ 3.14(尚未支持),或者因为你的 RDBMS 不支持 SQL/XML 或 SQL/JSON,你可以运行多个查询,并在你的代码中手动组装结果。
英文:
Using standard SQL JOIN
I'm assuming you're using jOOQ's code generator. You write a join just like you would write a join in SQL:
ctx.select() // Optionally, list columns here, explicitly
.from(USERS)
.join(USER_KEYS).on(USERS.ID.eq(USER_KEYS.USER_ID))
.join(KEYS).on(USER_KEYS.KEY_ID.eq(KEYS.ID))
.where(USERS.NAME.eq("something"))
.fetch();
Nesting collections
> What if additional tables exist (for instance UserRoles), etc. In general, how to fetch a user and all associated rows, related via foreign keys tables?
I'm not sure if this is still the same question. The above may have been about how to do joins in general, this one seems to be more specific about how to fetch nested collections?
Because a JOIN
will always produce cartesian products, which are undesirable, once you're joining several to-many paths. Starting from jOOQ 3.14, you can use SQL/XML or SQL/JSON as a workaround for this, if your database supports that. Starting from jOOQ 3.15, you can use MULTISET
. For example, the JSON solution might look like this:
List<User> users =
ctx.select(jsonObject(
jsonEntry("id", USERS.ID),
jsonEntry("name", USERS.NAME),
jsonEntry("keys", field(
select(jsonArrayAgg(jsonObject(KEYS.NAME, KEYS.ID)))
.from(KEYS)
.join(USER_KEYS).on(KEYS.ID.eq(USER_KEYS.KEY_ID))
.where(USER_KEYS.USER_ID.eq(USER.ID))
)),
jsonEntry("roles", field(
select(jsonArrayAgg(jsonObject(ROLES.NAME, ROLES.ID)))
.from(ROLES)
.join(USER_ROLES).on(ROLES.ID.eq(USER_ROLES.ROLE_ID))
.where(USER_ROLES.USER_ID.eq(USER.ID))
))
))
.from(USERS)
.where(USERS.NAME.eq("something"))
.fetchInto(User.class);
Assuming the User
class looks like this, and that you have Gson or Jackson on your classpath to map from JSON to your Java data structures:
class Key {
long id;
String name;
}
class Role {
long id;
String name;
}
class User {
long id;
String name;
List<Key> keys;
List<Role> roles;
}
Of course, you don't have to map to Java data structures and produce a JSON result directly, without further mapping. See also this blog post for more details, or this one explaining how to use MULTISET
.
Note that JSON_ARRAYAGG()
aggregates empty sets into NULL
, not into an empty []
. If that's a problem, use COALESCE()
The MULTISET
solution would look like this:
List<User> users =
ctx.select(
USERS.ID,
USERS.NAME,
multiset(
select(KEYS.NAME, KEYS.ID)
.from(KEYS)
.join(USER_KEYS).on(KEYS.ID.eq(USER_KEYS.KEY_ID))
.where(USER_KEYS.USER_ID.eq(USER.ID))
).as("keys").convertFrom(r -> r.map(Records.mapping(Key::new))),
multiset(
select(ROLES.NAME, ROLES.ID)
.from(ROLES)
.join(USER_ROLES).on(ROLES.ID.eq(USER_ROLES.ROLE_ID))
.where(USER_ROLES.USER_ID.eq(USER.ID))
).as("roles").convertFrom(r -> r.map(Records.mapping(Role::new)))
)
.from(USERS)
.where(USERS.NAME.eq("something"))
.fetch(Records.mapping(User::new));
The above approach using the various Records.mapping()
overloads along with ad-hoc data type conversion assumes the presence of an immutable constructor, such as you'd get if your classes were Java 16 records:
record Key (int id, String name) {}
record Role (int id, String name) {}
record User (int id, String name, List<Key> keys, List<Role> roles) {}
Using multiple queries
If you cannot use the above approach, because you can't work with jOOQ 3.14 (yet), or because your RDBMS doesn't support SQL/XML or SQL/JSON, you can run several queries and assemble the results manually on your end.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论