jooq 具有一对多关系的单个查询

Posted

技术标签:

【中文标题】jooq 具有一对多关系的单个查询【英文标题】:jooq single query with one to many relationship 【发布时间】:2016-02-24 00:52:07 【问题描述】:

我有一个表格实验和一个表格标签。一个实验可能有多个标签。 架构:

--------                  --------
|Table1|  1           n   |Table2|
|      | <--------------> |      |
|      |                  |      |
--------                  --------
(experiment)              (tags)

是否可以使用 jooq 创建返回实验和相应标签列表的查询?

类似于Result&lt;Record&gt;,其中Record 是一个experimentRecord 和一个标签列表,或者一个map&lt;experimentRecordList&lt;TagRecord&gt;

我也有一个查询,它只返回一个结果,有什么方便的吗?

编辑:java8,最新的 jooq。

【问题讨论】:

哪个 Java 版本,哪个 JooQ 版本?另外,你的桌子是什么? 【参考方案1】:

有很多方法可以使用 SQL 和/或 jOOQ 实现嵌套集合。我只是浏览了其中的一些:

使用连接

如果您没有深度嵌套这些集合,则使用 JOIN 对结果进行非规范化(展平)可能会为您解决问题,而不会在复制数据时增加太多开销。基本上,你会写:

Map<ExperimentRecord, Result<Record>> map =
DSL.using(configuration)
   .select()
   .from(EXPERIMENT)
   .join(TAGS)
   .on(...)
   .fetchGroups(EXPERIMENT);

上图包含实验记录作为键,嵌套集合包含所有标签作为值。

创建两个查询

如果您想具体化一个复杂的对象图,使用连接可能不再是最佳选择。相反,您可能希望从两个不同的查询中收集客户端中的数据:

Result<ExperimentRecord> experiments = 
DSL.using(configuration)
   .selectFrom(EXPERIMENT)
   .fetch();

Result<TagsRecord> tags =
DSL.using(configuration)
   .selectFrom(TAGS)
   .where(... restrict to the previous experiments ...)
   .fetch();
 

现在,将两个结果合并到客户的内存中,例如

experiments.stream()
           .map(e -> new ExperimentWithTags(
                e, 
                tags.stream()
                    .filter(t -> e.getId().equals(t.getExperimentId()))
                    .collect(Collectors.toList())
           ));

使用 SQL/XML 或 SQL/JSON 嵌套集合

这个问题不需要它,但其他人可能会在寻找与 jOOQ 嵌套对多关系的方法时发现这个问题。 I've provided an answer here。从 jOOQ 3.14 开始,您可以使用 RDBMS 的 SQL/XML 或 SQL/JSON 功能,然后使用 Jackson、Gson 或 JAXB 来嵌套集合,如下所示:

List<Experiment> experiments =
ctx.select(
      EXPERIMENT.asterisk(),
      field(
        select(jsonArrayAgg(jsonObject(TAGS.fields())))
        .from(TAGS)
        .where(TAGS.EXPERIMENT_ID.eq(EXPERIMENT.ID))
      ).as("tags")
    )
   .from(EXPERIMENT)
   .fetchInto(Experiment.class);

Experiment 是一个自定义 Java 类,如下所示:

class Experiment 
  long id;
  String name;
  List<Tag> tags;


class Tag 
  long id;
  String name;

使用MULTISET 嵌套集合

比上面的还要好,you can hide using SQL/XML or SQL/JSON behind jOOQ 3.15's new MULTISET operator support。假设上述 Java 类是 Java 16 记录(或任何其他不可变类),您甚至可以将嵌套集合类型安全地映射到您的 DTO:

List<Experiment> experiments =
ctx.select(
      EXPERIMENT.ID,
      EXPERIMENT.NAME,
      multiset(
        select(TAGS.ID, TAGS.NAME)
        .from(TAGS)
        .where(TAGS.EXPERIMENT_ID.eq(EXPERIMENT.ID))
      ).as("tags").convertFrom(r -> r.map(Records.mapping(Tag::new)))
    )
   .from(EXPERIMENT)
   .fetch(Records.mapping(Experiment::new));

Experiment 是一个自定义 Java 类,如下所示:

record Experiment(long id, String name, List<Tag> tags) 
record Tag(long id, String name) 

See also this blog post for more information.

【讨论】:

谢谢,我有时知道我只有一个结果,有没有捷径? @Leander:我不确定你的意思……什么结果? EXPERIMENT?还是TAGS @Leander:我明白了。您可以致电fetchOne()fetchOptional() 而不是fetch() 来获取个人记录 但是这样就没有把握掌握TAGS集合了吗? @tfeiner:对不起,我不确定我是否理解你所说的“掌握标签集合”的意思......?【参考方案2】:

您现在可以使用SimpleFlatMapper 将您的结果映射到Tuple2&lt;ExperimentRecord, List&lt;TagRecord&gt;&gt;。您需要做的就是。

1 - 创建一个映射器,指定键列,假设它是 id

JdbcMapper mapper = 
    JdbcMapperFactory
     .newInstance()
     .addKeys(EXPERIMENT.ID.getName())
     .newMapper(new TypeReference<Tuple2<ExperimentRecord, List<TagRecord>>>() );

2 - 在查询的 ResultSet 上使用映射器

try (ResultSet rs = DSL.using(configuration)
   .select()
   .from(EXPERIMENT)
   .join(TAGS)
   .on(...)
   .fetchResultSet()) 
    Stream<Tuple2<ExperimentRecord, List<TagRecord>>> stream = mapper.stream(rs);
    ....

详情请见here

【讨论】:

以上是关于jooq 具有一对多关系的单个查询的主要内容,如果未能解决你的问题,请参考以下文章

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

具有一对多关系的灵活查询

具有一对多关系的 GraphQL 循环查询

创建具有多个一对多关系的 DAO 查询?

具有一对多关系的复杂核心数据谓词

减少 MySQL 中具有许多一对多关系 (ORM) 的查询