web-dev-qa-db-ja.com

jOOQを使用したPostgreSQLのUPSERT

JOOQライブラリを使用してPostgreSQLでUPSERTを実行しようとしています。

これを行うために、私は現在、jOOQで次のSQLステートメントを実装しようとしています: https://stackoverflow.com/a/6527838

これまでのところ、私のコードは次のようになります。

public class UpsertExecutor {

    private static final Logger logger = LoggerFactory.getLogger(UpsertExecutor.class);

    private final JOOQContextProvider jooqProvider;

    @Inject
    public UpsertExecutor(JOOQContextProvider jooqProvider) {
        Preconditions.checkNotNull(jooqProvider);

        this.jooqProvider = jooqProvider;
    }

    @Transactional
    public <T extends Record> void executeUpsert(Table<T> table, Condition condition, Map<? extends Field<?>, ?> recordValues) {
        /*
         * All of this is for trying to do an UPSERT on PostgreSQL. See:
         * https://stackoverflow.com/a/6527838
         */

        SelectConditionStep<Record1<Integer>> notExistsSelect = jooqProvider.getDSLContext().selectOne().from(table).where(condition);
        SelectConditionStep<Record> insertIntoSelect = jooqProvider.getDSLContext().select(recordValues).whereNotExists(notExistsSelect);

        try {
            int[] result = jooqProvider.getDSLContext().batch(
                jooqProvider.getDSLContext().update(table).set(recordValues).where(condition),
                jooqProvider.getDSLContext().insertInto(table).select(insertIntoSelect)
            ).execute();

            long rowsAffectedTotal = 0;
            for (int rowsAffected : result) {
                rowsAffectedTotal += rowsAffected;
            }

            if (rowsAffectedTotal != 1) {
                throw new RuntimeException("Upsert must only affect 1 row. Affected: " + rowsAffectedTotal + ". Table: " + table + ". Condition: " + condition);
            }
        } catch (DataAccessException e) {
            if (e.getCause() instanceof BatchUpdateException) {
                BatchUpdateException cause = (BatchUpdateException)e.getCause();

                logger.error("Batch update error in upsert.", cause.getNextException());
            }

            throw e;
        }
    }
}

ただし、select()は値のマップをサポートしていないため、このコードはコンパイルされません。

SelectConditionStep<Record> insertIntoSelect = jooqProvider.getDSLContext().select(recordValues).whereNotExists(notExistsSelect);

質問

Select_)に次のような事前定義された値のセットを提供するにはどうすればよいですか:SELECT 3, 'C', 'Z'

アップデート1

私はなんとかコードを機能させることができました。ここに完全なクラスがあります:

public class UpsertExecutor {

    private static final Logger logger = LoggerFactory.getLogger(UpsertExecutor.class);

    private final JOOQContextProvider jooqProvider;

    @Inject
    public UpsertExecutor(JOOQContextProvider jooqProvider) {
        Preconditions.checkNotNull(jooqProvider);

        this.jooqProvider = jooqProvider;
    }

    @Transactional
    public <T extends Record> void executeUpsert(Table<T> table, Condition condition, List<FieldValue<Field<?>, ?>> recordValues) {
        /*
         * All of this is for trying to do an UPSERT on PostgreSQL. See:
         * https://stackoverflow.com/a/6527838
         */

        Map<Field<?>, Object> recordValuesMap = new HashMap<Field<?>, Object>();
        for (FieldValue<Field<?>, ?> entry : recordValues) {
            recordValuesMap.put(entry.getFieldName(), entry.getFieldValue());
        }

        List<Param<?>> params = new LinkedList<Param<?>>();
        for (FieldValue<Field<?>, ?> entry : recordValues) {
            params.add(val(entry.getFieldValue()));
        }

        List<Field<?>> fields = new LinkedList<Field<?>>();
        for (FieldValue<Field<?>, ?> entry : recordValues) {
            fields.add(entry.getFieldName());
        }

        SelectConditionStep<Record1<Integer>> notExistsSelect = jooqProvider.getDSLContext().selectOne().from(table).where(condition);
        SelectConditionStep<Record> insertIntoSelect = jooqProvider.getDSLContext().select(params).whereNotExists(notExistsSelect);

        try {
            int[] result = jooqProvider.getDSLContext().batch(
                jooqProvider.getDSLContext().update(table).set(recordValuesMap).where(condition),
                jooqProvider.getDSLContext().insertInto(table, fields).select(insertIntoSelect)
            ).execute();

            long rowsAffectedTotal = 0;
            for (int rowsAffected : result) {
                rowsAffectedTotal += rowsAffected;
            }

            if (rowsAffectedTotal != 1) {
                throw new RuntimeException("Upsert must only affect 1 row. Affected: " + rowsAffectedTotal + ". Table: " + table + ". Condition: " + condition);
            }
        } catch (DataAccessException e) {
            if (e.getCause() instanceof BatchUpdateException) {
                BatchUpdateException cause = (BatchUpdateException)e.getCause();

                logger.error("Batch update error in upsert.", cause.getNextException());
            }

            throw e;
        }
    }
}

ただし、List<FieldValue<Field<?>, ?>> recordValuesパラメーターではあまりクリーンに感じられません。これを行う方法についてのより良いアイデアはありますか?

36
uldall

jOOQ 3.7+は、PostgreSQL9.5のON CONFLICT句をサポートしています。

完全なPostgreSQLベンダー固有の構文はまだサポートされていませんが、MySQLまたはH2構文を使用できます。どちらも、PostgreSQLのON CONFLICTを使用してエミュレートできます。

MySQL INSERT .. ON DUPLICATE KEY UPDATE

DSL.using(configuration)
   .insertInto(TABLE)
   .columns(ID, A, B)
   .values(1, "a", "b")
   .onDuplicateKeyUpdate()
   .set(A, "a")
   .set(B, "b")
   .execute();

H2 MERGE INTO ..

DSL.using(configuration)
   .mergeInto(TABLE, A, B, C)
   .values(1, "a", "b")
   .execute();
18
Lukas Eder

次に、上記のLucasのUpdatableRecordオブジェクトのソリューションから派生したupsertユーティリティメソッドを示します。

public static int upsert(final DSLContext dslContext, final UpdatableRecord record) {
    return dslContext.insertInto(record.getTable())
                     .set(record)
                     .onDuplicateKeyUpdate()
                     .set(record)
                     .execute();
}
12
ud3sh

目的を達成するための少し複雑な方法のようです。単純な保存機能を使用しないのはなぜですか?アップサート関数の作成方法は postgresqlマニュアル に記載されており、Javaコードから呼び出すだけです。

4
e4c5

JOOQ 3.11、Kotlin、およびPostgreSQL DSLでの@ ud3shコメントに触発されました

これは、upsertオブジェクトでUpdatableRecordを直接呼び出す拡張関数です

import org.jooq.UpdatableRecord

internal fun UpdatableRecord<*>.upsert(): Int {
    if(this.configuration() == null) {
        throw NullPointerException("Attach configuration to record before calling upsert")
    }
    return this.configuration().dsl().insertInto(this.getTable()).set(this).onConflict().doUpdate().set(this).execute()
}
2
frno