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

2022-09-03 06:51:15

我有一个表实验和一个表标记。一个实验可能有多个标签。图式:

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

是否可以使用jooq创建一个查询,该查询返回实验和相应的标记列表?

例如,记录是实验记录和标签列表,或 , .Result<Record>map<experimentRecordList<TagRecord>

我还有一个只返回一个结果的查询,那里有方便的东西吗?

编辑:java8,最新的jooq。


答案 1

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

使用联接

如果您没有深入嵌套这些集合,则使用 a 对结果进行非规范化(平展化)可能会为您解决问题,而不会在重复数据时增加太多开销。从本质上讲,您将编写: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嵌套多关系的方法。我在这里提供了一个答案。从 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);

哪里是像这样的自定义 Java 类:Experiment

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

class Tag {
  long id;
  String name;
}

嵌套集合使用MULTISET

比上述更好的是,您可以在jOOQ 3.15的新MULTISET运算符支持后面隐藏使用SQL / XML或SQL / JSON。假设上述 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));

哪里是像这样的自定义 Java 类:Experiment

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

有关详细信息,另请参阅此博客文章


答案 2

现在,您可以使用 SimpleFlatMapper 将结果映射到 .您需要做的就是。Tuple2<ExperimentRecord, List<TagRecord>>

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

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

2 - 在查询的结果集上使用映射器

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

有关更多详细信息,请参阅此处


推荐