Подтвердить что ты не робот

Выберите вставить из значений() с правильным типом, используя jOOQ

Я использую 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) и выполняю вещь. Я точно знаю, что ссылочные ключи для person и company уже существуют, а исходный код также разрешает дубликаты, нам не нужно об этом беспокоиться.

Что меня беспокоит, это таблица 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(), непосредственно передавая поля где-нибудь?

4b9b3361

Ответ 1

Итак, values() в jOOQ дает вам конструктор таблицы. По умолчанию select() дает вам все столбцы, как они есть, что означает, что jOOQ будет определять как Field<object>

Я думаю, что думать об этом - это то, что jOOQ на данный момент - признать две вещи. Вы можете попытаться определить поля на выходе или вы можете принимать объекты и пытаться обрабатывать случаи типов на обратном пути. Мои предпочтения были бы для полей на выходе.

Один важный аспект для рассмотрения здесь состоит в том, что строки должны иметь одинаковые типы для данного столбца, но бывают случаи, когда неявные приведения в SQL могут конфликтовать с данными в jOOQ. Например, если вы вставляете числовое значение в первой строке и целое число во втором, целое число будет отбрасываться на числовое значение неявно. Определение полей для выбора позволяет избежать этой проблемы. Поэтому с точки зрения вопроса я бы определил поля отдельно и, в частности, их в разделе select from values. Это должно решить эту проблему. С помощью 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 генерирует отливки. Неизвестные могут стать лучниками, но затем будут корректно отображены.