使用 jOOQ 使用正确的类型转换从 values() 中选择插入

Posted

技术标签:

【中文标题】使用 jOOQ 使用正确的类型转换从 values() 中选择插入【英文标题】:select into insert from values() with correct type casts using jOOQ 【发布时间】:2018-02-18 15:35:01 【问题描述】:

我正在使用 jOOQ 将很多行插入到一个多对多关系的表中。代码有效,生成的 SQL 符合预期,我的问题是我希望 jOOQ 代码可以更简单。

我有一个简化的结构(所有内容都被重命名,大部分字段被移除,大部分约束被移除,这只是一个愚蠢但准确的结构示例):

CREATE TABLE person (
    person_id BIGSERIAL PRIMARY KEY,
    person_name VARCHAR(64) NOT NULL UNIQUE
);

CREATE TABLE company (
    company_id BIGSERIAL PRIMARY KEY,
    company_name VARCHAR(100) NOT NULL UNIQUE
);

CREATE TABLE employment_contract (
    company_id BIGINT NOT NULL REFERENCES company,
    person_id BIGINT NOT NULL REFERENCES person,
    PRIMARY KEY (company_id, person_id),

    salary INT NOT NULL,
    creation_date_time TIMESTAMP NOT NULL
);

我的插入代码:

Table<Record4<String, String, Integer, Timestamp>> insertValues = values(
    row(
        cast(null, COMPANY.COMPANY_NAME),
        cast(null, PERSON.PERSON_NAME),
        cast(null, EMPLOYMENT_CONTRACT.SALARY),
        cast(null, EMPLOYMENT_CONTRACT.CREATION_DATE_TIME)
    )
).as("insert_values",
        COMPANY.COMPANY_NAME.getName(),  -- these lines are bugging me
        PERSON.PERSON_NAME.getName(),
        EMPLOYMENT_CONTRACT.SALARY.getName(),
        EMPLOYMENT_CONTRACT.CREATION_DATE_TIME.getName()
);

Insert<AffectedSubscriberRecord> insert = insertInto(EMPLOYMENT_CONTRACT)
    .columns(EMPLOYMENT_CONTRACT.COMPANY_ID,
            EMPLOYMENT_CONTRACT.PERSON_ID,
            EMPLOYMENT_CONTRACT.SALARY,
            EMPLOYMENT_CONTRACT.CREATION_DATE_TIME
    )
    .select(
        select(
            COMPANY.COMPANY_ID,
            PERSON.PERSON_ID,
            insertValues.field(EMPLOYMENT_CONTRACT.SALARY),
            insertValues.field(EMPLOYMENT_CONTRACT.CREATION_DATE_TIME)
        )
        .from(insertValues)
            .join(COMPANY).using(COMPANY.COMPANY_NAME)
            .join(PERSON).using(PERSON.PERSON_NAME)
    );

然后我将所有行绑定到context.batch(insert) 并执行该操作。我确定personcompany 的引用键已经存在,并且原始代码也解决了重复,我们这里不需要关心这些东西。

让我烦恼的是insertValues 表 - 我需要在容易出错的复制粘贴中指定列类型和名称两次,使用 .getName() 调用会掩盖整个代码并且很容易错误地交换.我尝试的是:

Table<Record4<String, String, Integer, Timestamp>> insertValues = values(
    row( (String)null, (String)null, (Integer)null, (Timestamp)null )
).as("insert_values",
        COMPANY.COMPANY_NAME.getName(),
        PERSON.PERSON_NAME.getName(),
        EMPLOYMENT_CONTRACT.SALARY.getName(),
        EMPLOYMENT_CONTRACT.CREATION_DATE_TIME.getName()
);

这显然不起作用,jOOQ 和 Postgres 都不知道插入的类型,DB 猜测 varchar 并失败。我们需要 jOOQ 至少为查询中的第一行生成类型转换。再试一次:

Table<Record4<String, String, Integer, Timestamp>> insertValues = values(
    row( COMPANY.COMPANY_NAME, PERSON.PERSON_NAME, EMPLOYMENT_CONTRACT.SALARY, EMPLOYMENT_CONTRACT.CREATION_DATE_TIME )
).as("insert_values");

这将是炸弹。 JOOQ 以这种方式知道正确的类型,并且可以为我生成强制转换,所有代码重复都消失了,一切都安全了。然而,这也失败了。 JOOQ 不明白我给了它一排空值。

有没有什么方法可以在没有不干净的.getName() 调用的情况下实现相同(或等效)的结果查询,直接在某处传递字段?

【问题讨论】:

我认为这个问题已经在这篇文章***.com/questions/10264001/… 中讨论过了 @mohamedchaawa 这确实是一个类似的问题。不过,在这种情况下,我想将一些现有列别名到一个新表中。目前似乎不可能,我会向 jooq 提出罚单。 我认为插入 NULL 是模式设计的代码异味。它迫使您始终进行防御性地检查 null 的编程,这会使您的代码难以遵循。我想你这样做只是为了举例,但我想我会提到它。 @Stuporman 我这样做是为了批量插入绑定,jooq.org/doc/3.10/manual/sql-execution/batch-execution。 P.S.而不是cast(null, COMPANY.COMPANY_NAME),我们一直在使用param(COMPANY.COMPANY_NAME)。但其余的保持不变。 【参考方案1】:

所以 jOOQ 中的 values() 为您提供了一个表构造函数。默认情况下,select() 为您提供所有列,这意味着 jOOQ 将定义为 Field&lt;object&gt;

我认为考虑这一点的方式是 jOOQ 在这一点上是认识到两件事。您可以尝试在输出中定义字段,或者您可以接受对象并尝试在返回的路上处理类型案例。我的偏好是输出中的字段。

这里要考虑的一个重要方面是,对于给定的列,行必须具有相同的类型,但在某些情况下,SQL 中的隐式转换可能会与 jOOQ 中的隐式转换相混淆。例如,如果您在第一行插入一个数值,在第二行插入一个整数,则该整数将被隐式转换为数值。为选择定义字段可以避免这个问题。因此,就问题而言,我将分别定义字段,特别是在“从值选择”部分中定义它们。那应该可以解决这个问题。使用VALUES(),您已经靠自己了,jOOQ 无法安全地推断类型。

因此,您的代码应该变成:

Table<Record4<String, String, Integer, Timestamp>> insertValues = values(
    row(
        cast(null, COMPANY.COMPANY_NAME),
        cast(null, PERSON.PERSON_NAME),
        cast(null, EMPLOYMENT_CONTRACT.SALARY),
        cast(null, EMPLOYMENT_CONTRACT.CREATION_DATE_TIME)
    )
).as("insert_values", "company_name", "person_name", "salary", "creation_time");
Field<String> ivCompanyNmae = field("insert_values.company_name". Stirng.class);
Field<Integer> ivSalary = field("insert_values.salary", Integer.class);
...

Insert<AffectedSubscriberRecord> insert = insertInto(EMPLOYMENT_CONTRACT)
    .columns(EMPLOYMENT_CONTRACT.COMPANY_ID,
        EMPLOYMENT_CONTRACT.PERSON_ID,
        EMPLOYMENT_CONTRACT.SALARY,
        EMPLOYMENT_CONTRACT.CREATION_DATE_TIME
    )
    .select(
        select(
            COMPANY.COMPANY_ID,
            PERSON.PERSON_ID,
            ivSalary,
            ivCreatedTime
        )
        .from(insertValues)
            .join(COMPANY).using(COMPANY.COMPANY_NAME)
            .join(PERSON).using(PERSON.PERSON_NAME)
     );

这是 jOOQ 生成演员表的地方。未知数可以成为弓箭手,但随后会被明确正确地施放。

【讨论】:

是的,我想我可以忍受。谢谢。

以上是关于使用 jOOQ 使用正确的类型转换从 values() 中选择插入的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 jOOQ 通过 SQLite 正确生成日期和时间类型

如何在 jooq 中将数据转换为 Result<Record> 数据类型

如何在 jOOQ 中使用 formatJSON(JSONFormat) 正确格式化生成的 JSON 类型的常规结果?

如何使用基于 jsontype 属性的 jOOQ 从数据库中检索数据

如何使用 jOOQ gradle 插件将 postgres 中的 bigint[] 字段转换为类字段

使用 jooq/postgresql 从 json 中提取键/值对 - java