killbill-aplcache

util: switch to bulk insert for history entries Signed-off-by:

2/11/2019 6:12:40 AM

Details

diff --git a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
index 8bd523d..a56212e 100644
--- a/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/dao/HistorySqlDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2017 Groupon, Inc
- * Copyright 2014-2017 The Billing Project, LLC
+ * Copyright 2014-2019 Groupon, Inc
+ * Copyright 2014-2019 The Billing Project, LLC
  *
  * The Billing Project licenses this file to you under the Apache License, version 2.0
  * (the "License"); you may not use this file except in compliance with the
@@ -24,12 +24,14 @@ import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.util.entity.Entity;
 import org.killbill.billing.util.entity.dao.EntityModelDao;
-import org.skife.jdbi.v2.sqlobject.Bind;
 import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.GetGeneratedKeys;
+import org.skife.jdbi.v2.sqlobject.SqlBatch;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
-import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.sqlobject.customizers.BatchChunkSize;
 import org.skife.jdbi.v2.sqlobject.customizers.Define;
+import org.skife.jdbi.v2.util.LongMapper;
 
 public interface HistorySqlDao<M extends EntityModelDao<E>, E extends Entity> {
 
@@ -39,8 +41,10 @@ public interface HistorySqlDao<M extends EntityModelDao<E>, E extends Entity> {
     public List<EntityHistoryModelDao<M, E>> getHistoryForTargetRecordId(@Define("bypassMappingRegistryCache") final boolean bypassMappingRegistryCache,
                                                                          @Bind("targetRecordId") final long targetRecordId,
                                                                          @SmartBindBean InternalTenantContext context);
-    @SqlUpdate
-    @GetGeneratedKeys
-    public Long addHistoryFromTransaction(@EntityHistoryBinder EntityHistoryModelDao<M, E> history,
-                                          @SmartBindBean InternalCallContext context);
+
+    @SqlBatch
+    @BatchChunkSize(1000) // Arbitrary value, just a safety mechanism in case of very large datasets
+    @GetGeneratedKeys(value = LongMapper.class)
+    public List<Long> addHistoriesFromTransaction(@EntityHistoryBinder Iterable<EntityHistoryModelDao<M, E>> histories,
+                                                  @SmartBindBean InternalCallContext context);
 }
diff --git a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index feb9231..99bb03f 100644
--- a/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/org/killbill/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -393,12 +393,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
                     }
                     Preconditions.checkState(reHydratedEntities.size() == entityRecordIds.size(), "Wrong number of reHydratedEntities=%s (entityRecordIds=%s)", reHydratedEntities, entityRecordIds);
 
-                    final Collection<Long> auditTargetRecordIds = new ArrayList<>(entityRecordIds.size());
-                    for (final M reHydratedEntityModelDao : reHydratedEntities) {
-                        // TODO Could we do this in bulk too?
-                        final Long auditTargetRecordId = insertHistory(reHydratedEntityModelDao, changeType, context);
-                        auditTargetRecordIds.add(auditTargetRecordId);
-                    }
+                    final Collection<Long> auditTargetRecordIds = insertHistories(reHydratedEntities, changeType, context);
                     // Note: audit entries point to the history record id
                     insertAudits(auditTargetRecordIds, tableName, changeType, context);
 
@@ -506,11 +501,16 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         throw new IllegalStateException("TimeZoneAwareEntity should have been found among " + args);
     }
 
-    private Long insertHistory(final M reHydratedEntityModelDao, final ChangeType changeType, final InternalCallContext context) {
-        final EntityHistoryModelDao<M, E> history = new EntityHistoryModelDao<M, E>(reHydratedEntityModelDao, reHydratedEntityModelDao.getRecordId(), changeType, null, context.getCreatedDate());
-        final Long recordId = sqlDao.addHistoryFromTransaction(history, context);
+    private List<Long> insertHistories(final Iterable<M> reHydratedEntityModelDaos, final ChangeType changeType, final InternalCallContext context) {
+        final Collection<EntityHistoryModelDao<M, E>> histories = new LinkedList<EntityHistoryModelDao<M, E>>();
+        for (final M reHydratedEntityModelDao : reHydratedEntityModelDaos) {
+            final EntityHistoryModelDao<M, E> history = new EntityHistoryModelDao<M, E>(reHydratedEntityModelDao, reHydratedEntityModelDao.getRecordId(), changeType, null, context.getCreatedDate());
+            histories.add(history);
+        }
+
+        final List<Long> recordIds = sqlDao.addHistoriesFromTransaction(histories, context);
         printSQLWarnings();
-        return recordId;
+        return recordIds;
     }
 
     // Bulk insert all audit logs for this operation
diff --git a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
index 67fa072..c1c256f 100644
--- a/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/entity/dao/EntitySqlDao.sql.stg
@@ -358,7 +358,7 @@ order by <recordIdField("t.")> ASC
 ;
 >>
 
-addHistoryFromTransaction() ::= <<
+addHistoriesFromTransaction() ::= <<
 insert into <historyTableName()> (
   <idField("")>
 , <historyTableFields("")>
diff --git a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
index 56caa8d..0dbee69 100644
--- a/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
+++ b/util/src/test/java/org/killbill/billing/util/dao/TestStringTemplateInheritance.java
@@ -119,27 +119,27 @@ public class TestStringTemplateInheritance extends UtilTestSuiteNoDB {
                                                                "where t.tenant_record_id = :tenantRecordId\r?\n" +
                                                                "limit 1\r?\n" +
                                                                ";");
-        assertPattern(kombucha.getInstanceOf("addHistoryFromTransaction").render(), "insert into kombucha_history \\(\r?\n" +
-                                                                                    "  id\r?\n" +
-                                                                                    ", target_record_id\r?\n" +
-                                                                                    ", change_type\r?\n" +
-                                                                                    ", tea\r?\n" +
-                                                                                    ", mushroom\r?\n" +
-                                                                                    ", sugar\r?\n" +
-                                                                                    ", account_record_id\r?\n" +
-                                                                                    ", tenant_record_id\r?\n" +
-                                                                                    "\\)\r?\n" +
-                                                                                    "values \\(\r?\n" +
-                                                                                    "  :id\r?\n" +
-                                                                                    ", :targetRecordId\r?\n" +
-                                                                                    ", :changeType\r?\n" +
-                                                                                    ", :tea\r?\n" +
-                                                                                    ", :mushroom\r?\n" +
-                                                                                    ", :sugar\r?\n" +
-                                                                                    ", :accountRecordId\r?\n" +
-                                                                                    ", :tenantRecordId\r?\n" +
-                                                                                    "\\)\r?\n" +
-                                                                                    ";");
+        assertPattern(kombucha.getInstanceOf("addHistoriesFromTransaction").render(), "insert into kombucha_history \\(\r?\n" +
+                                                                                      "  id\r?\n" +
+                                                                                      ", target_record_id\r?\n" +
+                                                                                      ", change_type\r?\n" +
+                                                                                      ", tea\r?\n" +
+                                                                                      ", mushroom\r?\n" +
+                                                                                      ", sugar\r?\n" +
+                                                                                      ", account_record_id\r?\n" +
+                                                                                      ", tenant_record_id\r?\n" +
+                                                                                      "\\)\r?\n" +
+                                                                                      "values \\(\r?\n" +
+                                                                                      "  :id\r?\n" +
+                                                                                      ", :targetRecordId\r?\n" +
+                                                                                      ", :changeType\r?\n" +
+                                                                                      ", :tea\r?\n" +
+                                                                                      ", :mushroom\r?\n" +
+                                                                                      ", :sugar\r?\n" +
+                                                                                      ", :accountRecordId\r?\n" +
+                                                                                      ", :tenantRecordId\r?\n" +
+                                                                                      "\\)\r?\n" +
+                                                                                      ";");
 
         assertPattern(kombucha.getInstanceOf("insertAuditsFromTransaction").render(), "insert into audit_log \\(\r?\n" +
                                                                                       "id\r?\n" +