UPSERT in PostgreSQL using jOOQ
j'essaie d'effectuer un UPSERT dans PostgreSQL en utilisant la bibliothèque jOOQ.
pour ce faire, je suis en train d'essayer D'implémenter la déclaration SQL suivante dans jOOQ: https://stackoverflow.com/a/6527838
Mon code ressemble à ceci:
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;
}
}
}
ce code ne compile cependant pas, puisque select () ne supporte pas une map de valeurs:
SelectConditionStep<Record> insertIntoSelect = jooqProvider.getDSLContext().select(recordValues).whereNotExists(notExistsSelect);
La Question
Comment puis-je fournir select() avec un ensemble de valeurs prédéfinies comme ceci: SELECT 3, 'C', 'Z'
?
mise à Jour de 1
j'ai réussi à faire fonctionner le code. Voici la classe:
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;
}
}
}
il ne se sent cependant pas très propre avec le List<FieldValue<Field<?>, ?>> recordValues
paramètre. Toutes les meilleures idées sur la façon de faire cela?
3 réponses
jOOQ 3.7+ supporte PostgreSQL 9.5 ' s ON CONFLICT
l'article:
la syntaxe complète de PostgreSQL spécifique au vendeur n'est pas encore supportée, mais vous pouvez utiliser la syntaxe MySQL ou H2, qui peuvent toutes les deux être émulées en utilisant 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();
Voici une méthode d'utilité upsert dérivée de la solution de Lucas ci-dessus pour les objets UpdatableRecord:
public static int upsert(final DSLContext dslContext, final UpdatableRecord record) {
return dslContext.insertInto(record.getTable())
.set(record)
.onDuplicateKeyUpdate()
.set(record)
.execute();
}
semble une façon un peu compliquée d'atteindre l'objectif. Pourquoi ne pas utiliser une simple fonction stockée? comment créer une fonction upsert est décrit dans le manuel postgresql alors appelez-le à partir de votre code java.