From c32fc814f6b21b6fc2a5828f074c20f22fe21671 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Sat, 10 Feb 2024 12:00:32 +1300 Subject: [PATCH] Add support for insert/insertAll with both options and explicit transaction --- .../src/main/java/io/ebean/Database.java | 16 ++++- .../server/core/DefaultServer.java | 8 ++- .../ebean/xtest/internal/api/TDSpiServer.java | 10 +++ .../tests/insert/TestInsertOnConflict.java | 66 +++++++++++++++++++ 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/Database.java b/ebean-api/src/main/java/io/ebean/Database.java index b62c2ba56..bc3f0139b 100644 --- a/ebean-api/src/main/java/io/ebean/Database.java +++ b/ebean-api/src/main/java/io/ebean/Database.java @@ -1228,7 +1228,7 @@ public interface Database { void insert(Object bean); /** - * Insert the bean with options - typically ON CONFLICT DO UPDATE | DO NOTHING. + * Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING). *

* Currently, this is limited to use with Postgres only, *

@@ -1242,6 +1242,13 @@ public interface Database { */ void insert(Object bean, Transaction transaction); + /** + * Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction. + *

+ * Currently, this is limited to use with Postgres only, + */ + void insert(Object bean, InsertOptions insertOptions, Transaction transaction); + /** * Insert a collection of beans. If there is no current transaction one is created and used to * insert all the beans in the collection. @@ -1260,6 +1267,13 @@ public interface Database { */ void insertAll(Collection beans, Transaction transaction); + /** + * Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction. + *

+ * Currently, this is limited to use with Postgres only, + */ + void insertAll(Collection beans, InsertOptions options, Transaction transaction); + /** * Execute explicitly passing a transaction. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index dafb9b43b..34fc985de 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1666,6 +1666,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { persister.insert(checkEntityBean(bean), null, transaction); } + @Override + public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) { + persister.insert(checkEntityBean(bean), insertOptions, transaction); + } + @Override public void insertAll(Collection beans) { insertAll(beans, null, null); @@ -1681,7 +1686,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { insertAll(beans, null, transaction); } - private void insertAll(@Nullable Collection beans, InsertOptions options, @Nullable Transaction transaction) { + @Override + public void insertAll(@Nullable Collection beans, InsertOptions options, @Nullable Transaction transaction) { if (beans == null || beans.isEmpty()) { return; } diff --git a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java index a30dd080a..fefece847 100644 --- a/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java +++ b/ebean-test/src/test/java/io/ebean/xtest/internal/api/TDSpiServer.java @@ -463,6 +463,11 @@ public class TDSpiServer implements SpiServer { } + @Override + public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) { + + } + @Override public void insertAll(Collection beans) { @@ -478,6 +483,11 @@ public class TDSpiServer implements SpiServer { } + @Override + public void insertAll(Collection beans, InsertOptions options, Transaction transaction) { + + } + @Override public int execute(SqlUpdate updSql, Transaction transaction) { return 0; diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java index f13dd9b33..4490a87ef 100644 --- a/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java +++ b/ebean-test/src/test/java/org/tests/insert/TestInsertOnConflict.java @@ -3,6 +3,7 @@ package org.tests.insert; import io.ebean.DB; import io.ebean.Database; import io.ebean.InsertOptions; +import io.ebean.Transaction; import io.ebean.annotation.Platform; import io.ebean.test.LoggedSql; import io.ebean.xtest.BaseTestCase; @@ -23,6 +24,39 @@ class TestInsertOnConflict extends BaseTestCase { .getGeneratedKeys(true) .build(); + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertOnConflictUpdateExplicitTransaction() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + var bean = newBean("a@b.com"); + + try (Transaction txn = DB.createTransaction()) { + db.insert(bean, ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + assertThat(bean.getId()).isNotNull(); + + var bean2 = newBean("a@b.com"); + bean2.setOnlineStatus(false); + try (Transaction txn = DB.createTransaction()) { + db.insert(bean2, ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + assertThat(bean2.getId()).isEqualTo(bean.getId()); + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + + List list = db.find(EPersonOnline.class).findList(); + assertThat(list).hasSize(1); + assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated()); + } + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) @Test void insertOnConflictUpdate_when_noIdValue() { @@ -107,6 +141,38 @@ class TestInsertOnConflict extends BaseTestCase { assertThat(list).hasSize(4); } + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) + @Test + void insertAll_onConflictUpdate_explicitTransaction() { + Database db = DB.getDefault(); + db.truncate(EPersonOnline.class); + LoggedSql.start(); + + try (Transaction txn = DB.createTransaction()) { + txn.setBatchSize(3); + var bean = newBean("a1@b.com"); + var bean2 = newBean("a2@b.com"); + var bean3 = newBean("a3@b.com"); + var bean4 = newBean("a4@b.com"); + db.insertAll(List.of(bean, bean2, bean3, bean4), ON_CONFLICT_UPDATE, txn); + txn.commit(); + } + + var sql = LoggedSql.stop(); + assertThat(sql).hasSize(8); + assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(1)).contains(" -- bind"); + assertThat(sql.get(2)).contains(" -- bind"); + assertThat(sql.get(3)).contains(" -- bind"); + assertThat(sql.get(4)).contains(" -- executeBatch()"); + assertThat(sql.get(5)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated"); + assertThat(sql.get(6)).contains(" -- bind"); + assertThat(sql.get(7)).contains(" -- executeBatch()"); + + List list = db.find(EPersonOnline.class).orderBy("id").findList(); + assertThat(list).hasSize(4); + } + @ForPlatform({Platform.POSTGRES, Platform.YUGABYTE}) @Test void explicitConstraint() {