我有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
的基础
如果我没有解释清楚,请见谅!非常感谢任何帮助。
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
枚举是:
ENUM
类型生成根据您的喜好,使用 隐式连接 可以稍微简化您的查询吗?
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));
对于这个答案,我假设你正在使用代码生成器(你应该!),因为它会极大地促进此代码的类型安全,并使这个答案更具可读性。
以上大部分可以在没有代码生成的情况下完成(隐式连接除外),但我相信这个答案可以很好地证明它在类型安全方面的好处。
如果升级不是一种选择,一种方法是:
使用 .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);