From 44462e3513be5a743e99984c769574a3eb3ead4c Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Fri, 28 Jan 2022 09:46:03 +1300 Subject: [PATCH] Add maxInBinding - Oracle support for large delete by ids --- .../ebean/config/dbplatform/DatabasePlatform.java | 13 +++++++++++-- .../config/dbplatform/oracle/OraclePlatform.java | 1 + .../sqlserver/SqlServerBasePlatform.java | 1 + .../server/persist/DefaultPersister.java | 15 +++++++-------- .../java/org/tests/query/TestQueryFilterMany.java | 2 +- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java index d46f6dd37..3950411aa 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java @@ -55,12 +55,12 @@ public class DatabasePlatform { protected boolean supportsSavepointId = true; protected boolean useMigrationStoredProcedures = false; - + /** * Can we use native java time API objects in * {@link ResultSet#getObject(int, Class)} and * {@link PreparedStatement#setObject(int, Object)}. - * + * * Not all drivers (DB2 e.g.) will support this. */ protected boolean supportsNativeJavaTime = true; @@ -214,6 +214,8 @@ public class DatabasePlatform { */ protected PersistBatch persistBatchOnCascade = PersistBatch.ALL; + protected int maxInBinding; + /** * The maximum length of table names - used specifically when derived * default table names for intersection tables. @@ -370,6 +372,13 @@ public class DatabasePlatform { return inlineSqlUpdateLimit; } + /** + * Return the maximum number of bind values this database platform allows or zero for no limit. + */ + public int getMaxInBinding() { + return maxInBinding; + } + /** * Return the maximum table name length. *

diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java index e9dd57207..74f68ced7 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java @@ -23,6 +23,7 @@ public class OraclePlatform extends DatabasePlatform { super(); this.platform = Platform.ORACLE; this.supportsDeleteTableAlias = true; + this.maxInBinding = 1000; this.maxTableNameLength = 30; this.maxConstraintNameLength = 30; this.dbEncrypt = new OracleDbEncrypt(); diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java index eb3dad7be..4dd2077b2 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerBasePlatform.java @@ -27,6 +27,7 @@ abstract class SqlServerBasePlatform extends DatabasePlatform { // SQL Server unless we are using sequences this.dbEncrypt = new SqlServerDbEncrypt(); this.persistBatchOnCascade = PersistBatch.NONE; + this.maxInBinding = 2000; this.idInExpandedForm = true; this.selectCountWithAlias = true; this.selectCountWithColumnAlias = true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java index eaa1e1c95..07410134b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/DefaultPersister.java @@ -1,7 +1,6 @@ package io.ebeaninternal.server.persist; import io.ebean.*; -import io.ebean.annotation.Platform; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollection.ModifyListenMode; import io.ebean.bean.EntityBean; @@ -42,15 +41,15 @@ public final class DefaultPersister implements Persister { * Actually does the persisting work. */ private final PersistExecute persistExecute; - private final SpiEbeanServer server; - private final BeanDescriptorManager beanDescriptorManager; + private final int maxInBinding; public DefaultPersister(SpiEbeanServer server, Binder binder, BeanDescriptorManager descMgr) { this.server = server; this.beanDescriptorManager = descMgr; this.persistExecute = new DefaultPersistExecute(binder, server.config().getPersistBatchSize()); + this.maxInBinding = server.databasePlatform().getMaxInBinding(); } @Override @@ -645,18 +644,18 @@ public final class DefaultPersister implements Persister { DeleteMode deleteMode = (permanent || !descriptor.isSoftDelete()) ? DeleteMode.HARD : DeleteMode.SOFT; return delete(descriptor, null, idList, transaction, deleteMode); } - + /** * Delete by Id or a List of Id's. */ private int delete(BeanDescriptor descriptor, Object id, List idList, Transaction transaction, DeleteMode deleteMode) { int rows = 0; - if (server.databasePlatform().isPlatform(Platform.SQLSERVER)) { + if (maxInBinding > 0) { // SqlServer has a 2100 parameter limit, so delete max 2000 ids at once // this gives space up to 100 more query parameters. - while (idList != null && idList.size() > 2000) { - rows += deleteBatch(descriptor, id, idList.subList(0, 2000), transaction, deleteMode); - idList = idList.subList(2000, idList.size()); + while (idList != null && idList.size() > maxInBinding) { + rows += deleteBatch(descriptor, id, idList.subList(0, maxInBinding), transaction, deleteMode); + idList = idList.subList(maxInBinding, idList.size()); } } rows += deleteBatch(descriptor, id, idList, transaction, deleteMode); diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java index 967efb9ba..9460b1bf0 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryFilterMany.java @@ -282,7 +282,7 @@ public class TestQueryFilterMany extends BaseTestCase { List sql = LoggedSql.stop(); - assertThat(sql).hasSize(2); + assertThat(sql.size()).isGreaterThan(1); assertSql(sql.get(0)).contains(" from o_customer t0 left join contact t1 on t1.customer_id = t0.id where t1.first_name is not null order by t0.id; --bind()"); platformAssertIn(sql.get(1), " from contact_note t0 where (t0.contact_id)"); assertSql(sql.get(1)).contains(" and lower(t0.title) like");