JOOQ arrayAgg array_agg 字段作为对象

问题描述 投票:0回答:2

我有2个实体:

record Customer(String name, List<CustomerContact > contactHistory) {}

record CustomerContact(LocalDateTime contactAt, Contact.Type type) {
  public enum Type {
    TEXT_MESSAGE, EMAIL
  }
}

这些保存在具有 2 个表的模式中:

CREATE TABLE customer(
  "id".   BIGSERIAL PRIMARY KEY,
  "name"  TEXT      NOT NULL
);

CREATE TABLE customer_contact(
  "customer_id" BIGINT REFERENCES "customer" (ID) NOT NULL,
  "type"        TEXT                           NOT NULL,
  "contact_at"  TIMESTAMPTZ                    NOT NULL DEFAULT (now() AT TIME ZONE 'utc')
);

我想通过一次查询检索我的客户的详细信息,并使用

arrayAgg
方法将 contactHistory 添加到每个客户。我有这样的查询:

//pseudo code

DSL.select(field("customer.name"))
   .select(arrayAgg(field("customer_contact.contact_at")) //TODO How to aggregate both fields into a CustomerContact object
   .from(table("customer"))
   .join(table("customer_contact")).on(field("customer_contact.customer_id").eq("customer.id"))
   .groupBy(field("customer_contact.customer_id"))
   .fetchOptional()
   .map(asCustomer());

我遇到的问题是

arrayAgg
仅适用于单个字段。我想使用 2 个字段,并将它们绑定到一个对象中 (
CustomerContact
) 然后将其用作
arrayAgg

的基础

如果我没有解释清楚,请见谅!非常感谢任何帮助。

java postgresql jooq
2个回答
1
投票

与其使用

ARRAY_AGG
,不如使用更强大的
MULTISET_AGG
MULTISET
来充分利用 jOOQ 的类型安全功能?将其与 ad-hoc 转换 相结合,以实现到 Java 记录的类型安全映射,如本文所示。您的查询将如下所示:

使用
MULTISET_AGG

List<Customer> customers =
ctx.select(
        CUSTOMER.NAME,
        multisetAgg(CUSTOMER_CONTACT.CONTACT_AT, CUSTOMER_CONTACT.TYPE)
            .convertFrom(r -> r.map(Records.mapping(CustomerContact::new))))
   .from(CUSTOMER)
   .join(CUSTOMER_CONTACT).on(CUSTOMER_CONTACT.CUSTOMER_ID.eq(CUSTOMER.ID))
   .groupBy(CUSTOMER_CONTACT.CUSTOMER_ID)
   .fetch(Records.mapping(Customer::new));

注意整个查询类型检查。如果您更改有关查询或记录的任何内容,它将不再编译,从而为您提供额外的类型安全性。这是假设你是

Type
枚举是:

根据您的喜好,使用 隐式连接 可以稍微简化您的查询吗?

List<Customer> customers =
ctx.select(
        CUSTOMER_CONTACT.customer().NAME,
        multisetAgg(CUSTOMER_CONTACT.CONTACT_AT, CUSTOMER_CONTACT.TYPE)
            .convertFrom(r -> r.map(Records.mapping(CustomerContact::new))))
   .from(CUSTOMER_CONTACT)
   .groupBy(CUSTOMER_CONTACT.CUSTOMER_ID)
   .fetch(Records.mapping(Customer::new));

在这个查询中没什么大不了的,但是在更复杂的查询中,它可以降低复杂度。

使用
MULTISET

替代方法是嵌套查询而不是聚合,如下所示:

List<Customer> customers =
ctx.select(
        CUSTOMER.NAME,
        multiset(
            select(CUSTOMER_CONTACT.CONTACT_AT, CUSTOMER_CONTACT.TYPE)
            .from(CUSTOMER_CONTACT)
            .where(CUSTOMER_CONTACT.CUSTOMER_ID.eq(CUSTOMER.ID))
        ).convertFrom(r -> r.map(Records.mapping(CustomerContact::new))))
   .from(CUSTOMER)
   .fetch(Records.mapping(Customer::new));

代码生成

对于这个答案,我假设你正在使用代码生成器(你应该!),因为它会极大地促进此代码的类型安全,并使这个答案更具可读性。

以上大部分可以在没有代码生成的情况下完成(隐式连接除外),但我相信这个答案可以很好地证明它在类型安全方面的好处。


0
投票

如果升级不是一种选择,一种方法是:

使用 .arrayAgg(field()) 但不是“customer_contact.contact_at”,而是使用 json_build_object

arrayAgg(field(fieldsToJson(customer_contact.contact_at, customer_contact.FIELD_TWO))).as(CUSTOMER_CONTACT_LIST))
    private String fieldsToJson(List<TableField<?,?>> fields) {
        StringBuilder sb = new StringBuilder();
        sb.append("json_build_object(");
        for (Field<?> field : fields) {
            sb.append("'").append(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, field.getName())).append("'").append(",").append(field).append(",");
        }
        sb.replace(sb.length() - 1, sb.length(), ")");
        return sb.toString();
    }

然后在您的映射器中使用 ObjectMapper 将 JSON 转换为您的 CustomerContact 对象。

Object[] contacts = record.get(CUSTOMER_CONTACT_LIST, Object[].class);

然后对于每个:

objectMapper.readValue(contactObject.toString(), CustomerContact.class);
© www.soinside.com 2019 - 2024. All rights reserved.