【问题标题】:UPSERT in PostgreSQL using jOOQ使用 jOOQ 在 PostgreSQL 中进行 UPSERT
【发布时间】:2014-05-18 12:10:21
【问题描述】:

我正在尝试使用 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&lt;FieldValue&lt;Field&lt;?&gt;, ?&gt;&gt; recordValues 参数感觉不是很干净。关于如何做到这一点有更好的想法吗?

【问题讨论】:

  • 看来 val() 静态方法可能是我正在寻找的:jooq.org/javadoc/3.3.x/org/jooq/impl/…
  • 你知道你必须在重试循环中使用SERIALIZABLE 事务,或者锁定表,这样才能可靠地工作,对吧?
  • @CraigRinger 是的,我知道这一点。你能推荐一个更好的方法吗?我对这个问题的更好解决方案非常感兴趣。
  • sql select 3,'C','Z'; 与 sql values (3,'C','Z'); 非常相似,所以是的 val() 听起来是正确的方法。
  • 仅供参考 PosgreSQL 9.5 将具有 UPSERT。 wiki.postgresql.org/wiki/UPSERT

标签: java sql postgresql jooq upsert


【解决方案1】:

实现目标的方法似乎有点复杂。为什么不使用简单的存储函数? postgresql manual 中描述了如何创建 upsert 函数,然后只需从您的 java 代码中调用它。

【讨论】:

【解决方案2】:

jOOQ 3.7+ 支持 PostgreSQL 9.5 的 ON CONFLICT 子句:

尚不支持完整的 PostgreSQL 供应商特定语法,但您可以使用 MySQL 或 H2 语法,它们都可以使用 PostgreSQL 的ON CONFLICT 进行模拟:

MySQLINSERT .. 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();

H2MERGE INTO ..

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

【讨论】:

  • 从 Lucas 的 TableRecord 对象解决方案派生的实用程序:public static int upsert(final DSLContext dslContext, final UpdatableRecord record) { return dslContext.insertInto(record.getTable()).set(record).onDuplicateKeyUpdate().set(record).execute(); }
  • @ud3sh:嘿,谢谢你的提及。随时提供此作为完整答案。我相信它可能对其他人有用!
【解决方案3】:

这是一个 upsert 实用方法,源自 Lucas 的上述 UpdatableRecord 对象解决方案:

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

【讨论】:

    【解决方案4】:

    灵感来自 @ud3sh 对 JOOQ 3.11、Kotlin 和 PostgreSQL DSL 的评论

    这是直接在UpdatableRecord对象上调用upsert的扩展函数

    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()
    }
    

    【讨论】:

      猜你喜欢
      • 2017-06-03
      • 2021-01-09
      • 2015-06-08
      • 2016-07-24
      • 1970-01-01
      • 2022-08-04
      • 2018-06-29
      • 2020-07-22
      相关资源
      最近更新 更多