【发布时间】: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<FieldValue<Field<?>, ?>> recordValues 参数感觉不是很干净。关于如何做到这一点有更好的想法吗?
【问题讨论】:
-
看来 val() 静态方法可能是我正在寻找的:jooq.org/javadoc/3.3.x/org/jooq/impl/…
-
你知道你必须在重试循环中使用
SERIALIZABLE事务,或者锁定表,这样才能可靠地工作,对吧? -
@CraigRinger 是的,我知道这一点。你能推荐一个更好的方法吗?我对这个问题的更好解决方案非常感兴趣。
-
sql
select 3,'C','Z';与 sqlvalues (3,'C','Z');非常相似,所以是的val()听起来是正确的方法。 -
仅供参考 PosgreSQL 9.5 将具有 UPSERT。 wiki.postgresql.org/wiki/UPSERT
标签: java sql postgresql jooq upsert