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" +