killbill-aplcache

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
index b0fe792..07f22f0 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/DefaultInvoiceDao.java
@@ -773,7 +773,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     throw new InvoiceApiException(ErrorCode.PAYMENT_NO_SUCH_PAYMENT, paymentId);
                 }
 
-                transactional.updateAttempt(invoicePayment.getRecordId(),
+                transactional.updateAttempt(invoicePayment.getId().toString(),
                                             invoicePayment.getPaymentId().toString(),
                                             invoicePayment.getPaymentDate().toDate(),
                                             invoicePayment.getAmount(),
@@ -932,7 +932,7 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                     if (existingAttempt == null) {
                         createAndRefresh(transactional, invoicePayment, context);
                     } else {
-                        transactional.updateAttempt(existingAttempt.getRecordId(),
+                        transactional.updateAttempt(existingAttempt.getId().toString(),
                                                     invoicePayment.getPaymentId().toString(),
                                                     invoicePayment.getPaymentDate().toDate(),
                                                     invoicePayment.getAmount(),
@@ -1219,7 +1219,17 @@ public class DefaultInvoiceDao extends EntityDaoBase<InvoiceModelDao, Invoice, I
                 // Deactivate any usage trackingIds if necessary
                 } else if (InvoiceStatus.VOID.equals(newStatus)) {
                     final InvoiceTrackingSqlDao trackingSqlDao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
-                    trackingSqlDao.deactivateForInvoice(invoiceId.toString(), context);
+                    final List<InvoiceTrackingModelDao> invoiceTrackingModelDaos = trackingSqlDao.getTrackingsForInvoice(invoiceId.toString(), context);
+                    if (!invoiceTrackingModelDaos.isEmpty()) {
+                        final Collection<String> invoiceTrackingIdsToDeactivate = Collections2.<InvoiceTrackingModelDao, String>transform(invoiceTrackingModelDaos,
+                                                                                                                                          new Function<InvoiceTrackingModelDao, String>() {
+                                                                                                                                              @Override
+                                                                                                                                              public String apply(final InvoiceTrackingModelDao input) {
+                                                                                                                                                  return input.getId().toString();
+                                                                                                                                              }
+                                                                                                                                          });
+                        trackingSqlDao.deactivateByIds(invoiceTrackingIdsToDeactivate, context);
+                    }
                 }
                 return null;
             }
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
index 2723b68..b530b30 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 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
@@ -26,6 +26,8 @@ import java.util.UUID;
 import org.killbill.billing.callcontext.InternalTenantContext;
 import org.killbill.billing.catalog.api.Currency;
 import org.killbill.billing.invoice.api.InvoicePayment;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.entity.dao.Audited;
 import org.killbill.billing.util.entity.dao.EntitySqlDao;
 import org.killbill.commons.jdbi.binder.SmartBindBean;
 import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
@@ -69,7 +71,8 @@ public interface InvoicePaymentSqlDao extends EntitySqlDao<InvoicePaymentModelDa
                                                            @SmartBindBean final InternalTenantContext context);
 
     @SqlUpdate
-    void updateAttempt(@Bind("recordId") Long recordId,
+    @Audited(ChangeType.UPDATE)
+    void updateAttempt(@Bind("id") String id,
                        @Bind("paymentId") final String paymentId,
                        @Bind("paymentDate") final Date paymentDate,
                        @Bind("amount") final BigDecimal amount,
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
index 49e8b8e..f1eeab5 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.java
@@ -17,6 +17,7 @@
 
 package org.killbill.billing.invoice.dao;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
@@ -31,14 +32,15 @@ import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
+import org.skife.jdbi.v2.unstable.BindIn;
 
 @KillBillSqlDaoStringTemplate
 public interface InvoiceTrackingSqlDao extends EntitySqlDao<InvoiceTrackingModelDao, Entity> {
 
     @SqlUpdate
     @Audited(ChangeType.DELETE)
-    public void deactivateForInvoice(@Bind("invoiceId") String invoiceId,
-                                     @SmartBindBean final InternalCallContext context);
+    public void deactivateByIds(@BindIn("ids") final Collection<String> ids,
+                                @SmartBindBean final InternalCallContext context);
 
     @SqlQuery
     List<InvoiceTrackingModelDao> getTrackingsByDateRange(@Bind("startDate") final Date startDate,
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
index 3552f3c..23776fa 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoicePaymentSqlDao.sql.stg
@@ -131,7 +131,7 @@ updateAttempt() ::= <<
     payment_cookie_id = :paymentCookieId,
     linked_invoice_payment_id := :linkedInvoicePaymentId,
     success := :success
-    WHERE record_id = :recordId
+    WHERE id = :id
     <AND_CHECK_TENANT("")>
     ;
 >>
diff --git a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
index 9add3fe..b20293a 100644
--- a/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
+++ b/invoice/src/main/resources/org/killbill/billing/invoice/dao/InvoiceTrackingSqlDao.sql.stg
@@ -28,14 +28,13 @@ tableValues() ::= <<
 , :updatedDate
 >>
 
-deactivateForInvoice() ::= <<
+deactivateByIds() ::= <<
 update <tableName()>
 set
-is_active = false
+  is_active = false
 , updated_by = :createdBy
 , updated_date = :updatedDate
-where
-invoice_id = :invoiceId
+where <idField("")> in (<ids>)
 <AND_CHECK_TENANT("")>
 ;
 >>
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
index 0072f3d..8c42ce5 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/dao/TestInvoiceTrackingSqlDao.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 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,15 +24,33 @@ import java.util.UUID;
 import org.joda.time.LocalDate;
 import org.killbill.billing.callcontext.InternalCallContext;
 import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.audit.dao.AuditLogModelDao;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
+import org.killbill.billing.util.dao.TableName;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.google.common.collect.ImmutableList;
+
 public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
 
+    private EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
+
+    @BeforeMethod(groups = "slow")
+    public void setUp() {
+        if (hasFailed()) {
+            return;
+        }
+        transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, roDbi, clock, new CacheControllerDispatcher(), nonEntityDao, internalCallContextFactory);
+    }
+
     @Test(groups = "slow")
     public void testBasicTrackingIds() {
-        final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
-
         LocalDate startRange = new LocalDate(2018, 8, 1);
         LocalDate endRange = new LocalDate(2018, 11, 23);
 
@@ -57,32 +75,39 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
         inputs.add(input3);
         inputs.add(input4);
 
-        dao.create(inputs, internalCallContext);
+        transactionalSqlDao.execute(false,
+                                    new EntitySqlDaoTransactionWrapper<Void>() {
+                                        @Override
+                                        public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                                            final InvoiceTrackingSqlDao dao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+
+                                            dao.create(inputs, internalCallContext);
 
-        final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
-        Assert.assertEquals(result.size(), 3);
+                                            final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+                                            Assert.assertEquals(result.size(), 3);
 
-        Assert.assertEquals(result.get(0).getTrackingId(), "trackingId1");
-        Assert.assertEquals(result.get(0).getInvoiceId(), invoiceId1);
-        Assert.assertEquals(result.get(0).getRecordDate(), startRange);
-        Assert.assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
+                                            Assert.assertEquals(result.get(0).getTrackingId(), "trackingId1");
+                                            Assert.assertEquals(result.get(0).getInvoiceId(), invoiceId1);
+                                            Assert.assertEquals(result.get(0).getRecordDate(), startRange);
+                                            Assert.assertEquals(result.get(0).getSubscriptionId(), subscriptionId);
 
-        Assert.assertEquals(result.get(1).getTrackingId(), "trackingId2");
-        Assert.assertEquals(result.get(1).getInvoiceId(), invoiceId1);
-        Assert.assertEquals(result.get(1).getRecordDate(), new LocalDate(2018, 8, 5));
-        Assert.assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
+                                            Assert.assertEquals(result.get(1).getTrackingId(), "trackingId2");
+                                            Assert.assertEquals(result.get(1).getInvoiceId(), invoiceId1);
+                                            Assert.assertEquals(result.get(1).getRecordDate(), new LocalDate(2018, 8, 5));
+                                            Assert.assertEquals(result.get(1).getSubscriptionId(), subscriptionId);
 
-        Assert.assertEquals(result.get(2).getTrackingId(), "trackingId3");
-        Assert.assertEquals(result.get(2).getInvoiceId(), invoiceId2);
-        Assert.assertEquals(result.get(2).getRecordDate(), new LocalDate(2018, 9, 1));
-        Assert.assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
+                                            Assert.assertEquals(result.get(2).getTrackingId(), "trackingId3");
+                                            Assert.assertEquals(result.get(2).getInvoiceId(), invoiceId2);
+                                            Assert.assertEquals(result.get(2).getRecordDate(), new LocalDate(2018, 9, 1));
+                                            Assert.assertEquals(result.get(2).getSubscriptionId(), subscriptionId);
 
+                                            return null;
+                                        }
+                                    });
     }
 
     @Test(groups = "slow")
     public void testInvalidation() {
-        final InvoiceTrackingSqlDao dao = dbi.onDemand(InvoiceTrackingSqlDao.class);
-
         LocalDate startRange = new LocalDate(2019, 1, 1);
         LocalDate endRange = new LocalDate(2019, 1, 31);
 
@@ -104,29 +129,56 @@ public class TestInvoiceTrackingSqlDao extends InvoiceTestSuiteWithEmbeddedDB {
         inputs.add(input3);
         inputs.add(input4);
 
-        dao.create(inputs, internalCallContext);
-
-        final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
-        Assert.assertEquals(result.size(), 4);
-
-        clock.addDays(1);
-        final InternalCallContext updatedContext = new InternalCallContext(internalCallContext.getTenantRecordId(),
-                                                                           internalCallContext.getAccountRecordId(),
-                                                                           internalCallContext.getFixedOffsetTimeZone(),
-                                                                           clock.getUTCNow(),
-                                                                           internalCallContext.getUserToken(),
-                                                                           "invalidation-user",
-                                                                           internalCallContext.getCallOrigin(),
-                                                                           internalCallContext.getContextUserType(),
-                                                                           internalCallContext.getReasonCode(),
-                                                                           internalCallContext.getComments(),
-                                                                           internalCallContext.getCreatedDate(),
-                                                                           clock.getUTCNow());
-
-        dao.deactivateForInvoice(invoiceId1.toString(), updatedContext);
-
-        final List<InvoiceTrackingModelDao> result2 = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
-        Assert.assertEquals(result2.size(), 1);
-
+        transactionalSqlDao.execute(false,
+                                    new EntitySqlDaoTransactionWrapper<Void>() {
+                                        @Override
+                                        public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                                            final InvoiceTrackingSqlDao dao = entitySqlDaoWrapperFactory.become(InvoiceTrackingSqlDao.class);
+
+                                            dao.create(inputs, internalCallContext);
+
+                                            final List<InvoiceTrackingModelDao> result = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+                                            Assert.assertEquals(result.size(), 4);
+
+                                            final List<AuditLogModelDao> auditLogsPostCreate = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
+                                            Assert.assertEquals(auditLogsPostCreate.size(), 4);
+                                            for (int i = 0; i < 4; i++) {
+                                                Assert.assertEquals(auditLogsPostCreate.get(i).getChangeType(), ChangeType.INSERT);
+                                                Assert.assertEquals(auditLogsPostCreate.get(i).getTargetRecordId(), result.get(i).getRecordId());
+                                            }
+
+                                            clock.addDays(1);
+                                            final InternalCallContext updatedContext = new InternalCallContext(internalCallContext.getTenantRecordId(),
+                                                                                                               internalCallContext.getAccountRecordId(),
+                                                                                                               internalCallContext.getFixedOffsetTimeZone(),
+                                                                                                               clock.getUTCNow(),
+                                                                                                               internalCallContext.getUserToken(),
+                                                                                                               "invalidation-user",
+                                                                                                               internalCallContext.getCallOrigin(),
+                                                                                                               internalCallContext.getContextUserType(),
+                                                                                                               internalCallContext.getReasonCode(),
+                                                                                                               internalCallContext.getComments(),
+                                                                                                               internalCallContext.getCreatedDate(),
+                                                                                                               clock.getUTCNow());
+
+                                            dao.deactivateByIds(ImmutableList.<String>of(input1.getId().toString(), input2.getId().toString(), input3.getId().toString()), updatedContext);
+
+                                            final List<InvoiceTrackingModelDao> result2 = dao.getTrackingsByDateRange(startRange.toDate(), endRange.toDate(), internalCallContext);
+                                            Assert.assertEquals(result2.size(), 1);
+
+                                            final List<AuditLogModelDao> auditLogsPostDelete = ImmutableList.<AuditLogModelDao>copyOf(dao.getAuditLogsForTableNameAndAccountRecordId(TableName.INVOICE_TRACKING_IDS.toString(), internalCallContext));
+                                            Assert.assertEquals(auditLogsPostDelete.size(), 7);
+                                            for (int i = 0; i < 4; i++) {
+                                                Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.INSERT);
+                                                Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i).getRecordId());
+                                            }
+                                            for (int i = 4; i < 7; i++) {
+                                                Assert.assertEquals(auditLogsPostDelete.get(i).getChangeType(), ChangeType.DELETE);
+                                                Assert.assertEquals(auditLogsPostDelete.get(i).getTargetRecordId(), result.get(i - 4).getRecordId());
+                                            }
+
+                                            return null;
+                                        }
+                                    });
     }
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
index 587d390..8d5de31 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/BundleSqlDao.java
@@ -1,7 +1,9 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
+ * Copyright 2014-2019 Groupon, Inc
+ * Copyright 2014-2019 The Billing Project, LLC
  *
- * Ning licenses this file to you under the Apache License, version 2.0
+ * 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
  * License.  You may obtain a copy of the License at:
  *
@@ -16,6 +18,7 @@
 
 package org.killbill.billing.subscription.engine.dao;
 
+import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 
@@ -32,6 +35,7 @@ import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
 import org.skife.jdbi.v2.sqlobject.customizers.Define;
+import org.skife.jdbi.v2.unstable.BindIn;
 
 @KillBillSqlDaoStringTemplate
 public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, SubscriptionBaseBundle> {
@@ -44,7 +48,7 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
 
     @SqlUpdate
     @Audited(ChangeType.UPDATE)
-    public void renameBundleExternalKey(@Bind("externalKey") String externalKey,
+    public void renameBundleExternalKey(@BindIn("ids") final Collection<String> ids,
                                         @Define("prefix") final String prefix,
                                         @SmartBindBean final InternalCallContext context);
 
@@ -64,6 +68,10 @@ public interface BundleSqlDao extends EntitySqlDao<SubscriptionBundleModelDao, S
                                                                  @SmartBindBean final InternalTenantContext context);
 
     @SqlQuery
+    public List<SubscriptionBundleModelDao> getBundlesForKey(@Bind("externalKey") String externalKey,
+                                                             @SmartBindBean final InternalTenantContext context);
+
+    @SqlQuery
     public List<SubscriptionBundleModelDao> getBundlesForLikeKey(@Bind("externalKey") String externalKey,
                                                                  @SmartBindBean final InternalTenantContext context);
 }
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index 31d1c4d..fe31f97 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -323,9 +323,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
                                 throw new SubscriptionBaseApiException(ErrorCode.SUB_CREATE_ACTIVE_BUNDLE_KEY_EXISTS, bundle.getExternalKey());
                             } else if (renameCancelledBundleIfExist) {
                                 log.info("Renaming bundles with externalKey='{}', prefix='cncl'", bundle.getExternalKey());
-                                // Note that if bundle belongs to a different account, context is not the context for this target account,
-                                // but the underlying sql operation does not use the account info
-                                bundleSqlDao.renameBundleExternalKey(bundle.getExternalKey(), "cncl", context);
+                                renameBundleExternalKey(bundleSqlDao, bundle.getExternalKey(), "cncl", context);
                             } /* else {
                                 Code will throw SQLIntegrityConstraintViolationException because of unique constraint on externalKey; might be worth having an ErrorCode just for that
                             } */
@@ -346,6 +344,22 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
         });
     }
 
+    // Note that if bundle belongs to a different account, context is not the context for this target account,
+    // but the underlying sql operation does not use the account info
+    private void renameBundleExternalKey(final BundleSqlDao bundleSqlDao, final String externalKey, final String prefix, final InternalCallContext context) {
+        final List<SubscriptionBundleModelDao> bundleModelDaos = bundleSqlDao.getBundlesForKey(externalKey, context);
+        if (!bundleModelDaos.isEmpty()) {
+            final Collection<String> bundleIdsToRename = Collections2.<SubscriptionBundleModelDao, String>transform(bundleModelDaos,
+                                                                                                                    new Function<SubscriptionBundleModelDao, String>() {
+                                                                                                                        @Override
+                                                                                                                        public String apply(final SubscriptionBundleModelDao input) {
+                                                                                                                            return input.getId().toString();
+                                                                                                                        }
+                                                                                                                    });
+            bundleSqlDao.renameBundleExternalKey(bundleIdsToRename, prefix, context);
+        }
+    }
+
     @Override
     public SubscriptionBase getBaseSubscription(final UUID bundleId, final Catalog catalog, final InternalTenantContext context) throws CatalogApiException {
         return getBaseSubscription(bundleId, true, catalog, context);
@@ -1012,7 +1026,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
 
                 // Rename externalKey from source bundle
                 final BundleSqlDao bundleSqlDao = entitySqlDaoWrapperFactory.become(BundleSqlDao.class);
-                bundleSqlDao.renameBundleExternalKey(bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
+                renameBundleExternalKey(bundleSqlDao, bundleTransferData.getData().getExternalKey(), "tsf", fromContext);
 
                 final SubscriptionEventSqlDao transactional = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class);
                 transferBundleDataFromTransaction(bundleTransferData, transactional, entitySqlDaoWrapperFactory, toContext);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
index ecfeb0f..886d776 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/BundleSqlDao.sql.stg
@@ -51,8 +51,17 @@ where id = :id
 renameBundleExternalKey(prefix)  ::= <<
 update bundles
 set external_key = concat('kb', '<prefix>', '-', record_id, ':', external_key)
+where <idField("")> in (<ids>)
+<AND_CHECK_TENANT("")>
+;
+>>
+
+getBundlesForKey() ::= <<
+select <allTableFields("")>
+from bundles
 where external_key = :externalKey
 <AND_CHECK_TENANT("")>
+<defaultOrderBy("")>
 ;
 >>
 
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
index 7ca3747..4e0eef5 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/engine/dao/TestSubscriptionDao.java
@@ -1,6 +1,6 @@
 /*
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 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
@@ -22,6 +22,7 @@ import java.util.UUID;
 
 import org.joda.time.DateTime;
 import org.killbill.billing.ErrorCode;
+import org.killbill.billing.ObjectType;
 import org.killbill.billing.account.api.Account;
 import org.killbill.billing.account.api.AccountData;
 import org.killbill.billing.api.TestApiListener.NextEvent;
@@ -40,7 +41,14 @@ import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
 import org.killbill.billing.subscription.events.user.ApiEventBuilder;
 import org.killbill.billing.subscription.events.user.ApiEventCreate;
 import org.killbill.billing.util.UUIDs;
+import org.killbill.billing.util.api.AuditLevel;
+import org.killbill.billing.util.audit.AuditLog;
+import org.killbill.billing.util.audit.ChangeType;
+import org.killbill.billing.util.cache.CacheControllerDispatcher;
 import org.killbill.billing.util.entity.dao.DBRouterUntyped;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 import org.killbill.commons.profiling.Profiling.WithProfilingCallback;
 import org.mockito.Mockito;
 import org.skife.jdbi.v2.IDBI;
@@ -56,6 +64,7 @@ import static org.testng.Assert.assertEquals;
 
 public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
 
+    private EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
     protected UUID accountId;
 
     @Override
@@ -72,6 +81,8 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         final AccountData accountData = subscriptionTestInitializer.initAccountData(clock);
         final Account account = createAccount(accountData);
         accountId = account.getId();
+
+        transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, roDbi, clock, new CacheControllerDispatcher(), nonEntityDao, internalCallContextFactory);
     }
 
     @Override // to ignore events
@@ -154,26 +165,55 @@ public class TestSubscriptionDao extends SubscriptionTestSuiteWithEmbeddedDB {
         assertEquals(result.size(), 1);
         assertEquals(result.get(0).getExternalKey(), bundle.getExternalKey());
 
+        final List<AuditLog> auditLogsBeforeRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogsBeforeRenaming.size(), 1);
+        assertEquals(auditLogsBeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
+
         // Update key to 'internal KB value 'kbtsf-12345:'
         dao.updateBundleExternalKey(bundle.getId(), "kbtsf-12345:" + bundle.getExternalKey(), internalCallContext);
         final List<SubscriptionBaseBundle> result2 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
         assertEquals(result2.size(), 1);
 
+        final List<AuditLog> auditLogsAfterRenaming = auditUserApi.getAuditLogs(bundle.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogsAfterRenaming.size(), 2);
+        assertEquals(auditLogsAfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
+        assertEquals(auditLogsAfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
+
         // Create new bundle with original key, verify all results show original key, stripping down internal prefix
         final DefaultSubscriptionBaseBundle bundleDef2 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
         final SubscriptionBaseBundle bundle2 = dao.createSubscriptionBundle(bundleDef2, catalog, true, internalCallContext);
         final List<SubscriptionBaseBundle> result3 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
         assertEquals(result3.size(), 2);
+        assertEquals(result3.get(0).getId(), bundle.getId());
         assertEquals(result3.get(0).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result3.get(1).getId(), bundle2.getId());
         assertEquals(result3.get(1).getExternalKey(), bundle2.getExternalKey());
 
+        final List<AuditLog> auditLogs2BeforeRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogs2BeforeRenaming.size(), 1);
+        assertEquals(auditLogs2BeforeRenaming.get(0).getChangeType(), ChangeType.INSERT);
+
         // This time we call the lower SqlDao to rename the bundle automatically and verify we still get same # results,
         // with original key
-        dbi.onDemand(BundleSqlDao.class).renameBundleExternalKey(externalKey, "foo", internalCallContext);
+        transactionalSqlDao.execute(false,
+                                    new EntitySqlDaoTransactionWrapper<Void>() {
+                                        @Override
+                                        public Void inTransaction(final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory) throws Exception {
+                                            entitySqlDaoWrapperFactory.become(BundleSqlDao.class).renameBundleExternalKey(ImmutableList.<String>of(bundle2.getId().toString()), "foo", internalCallContext);
+                                            return null;
+                                        }
+                                    });
         final List<SubscriptionBaseBundle> result4 = dao.getSubscriptionBundlesForKey(externalKey, internalCallContext);
         assertEquals(result4.size(), 2);
         assertEquals(result4.get(0).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result4.get(0).getId(), bundle.getId());
         assertEquals(result4.get(1).getExternalKey(), bundle2.getExternalKey());
+        assertEquals(result4.get(1).getId(), bundle2.getId());
+
+        final List<AuditLog> auditLogs2AfterRenaming = auditUserApi.getAuditLogs(bundle2.getId(), ObjectType.BUNDLE, AuditLevel.FULL, callContext);
+        assertEquals(auditLogs2AfterRenaming.size(), 2);
+        assertEquals(auditLogs2AfterRenaming.get(0).getChangeType(), ChangeType.INSERT);
+        assertEquals(auditLogs2AfterRenaming.get(1).getChangeType(), ChangeType.UPDATE);
 
         // Create bundle one more time
         final DefaultSubscriptionBaseBundle bundleDef3 = new DefaultSubscriptionBaseBundle(externalKey, accountId, startDate, startDate, createdDate, createdDate);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
index 5a59f67..260c0da 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/SubscriptionTestSuiteWithEmbeddedDB.java
@@ -1,7 +1,7 @@
 /*
  * Copyright 2010-2013 Ning, Inc.
- * Copyright 2014-2018 Groupon, Inc
- * Copyright 2014-2018 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
@@ -41,6 +41,7 @@ import org.killbill.billing.subscription.api.user.TestSubscriptionHelper;
 import org.killbill.billing.subscription.engine.addon.AddonUtils;
 import org.killbill.billing.subscription.engine.dao.SubscriptionDao;
 import org.killbill.billing.subscription.glue.TestDefaultSubscriptionModuleWithEmbeddedDB;
+import org.killbill.billing.util.api.AuditUserApi;
 import org.killbill.billing.util.config.definition.SubscriptionConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.bus.api.PersistentBus;
@@ -91,6 +92,8 @@ public class SubscriptionTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuiteW
     protected TestApiListener testListener;
     @Inject
     protected SubscriptionTestInitializer subscriptionTestInitializer;
+    @Inject
+    protected AuditUserApi auditUserApi;
 
     @Inject
     protected NonEntityDao nonEntityDao;
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 0378027..b4b8a0e 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
@@ -59,6 +59,7 @@ import org.skife.jdbi.v2.exceptions.DBIException;
 import org.skife.jdbi.v2.exceptions.StatementException;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlBatch;
+import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.unstable.BindIn;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -184,6 +185,9 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
     private Object invokeSafely(final Method method, final Object[] args) throws Throwable {
         final Audited auditedAnnotation = method.getAnnotation(Audited.class);
 
+        final boolean isROQuery = method.getAnnotation(SqlQuery.class) != null;
+        Preconditions.checkState(auditedAnnotation != null || isROQuery, "Non-@SqlQuery method %s without @Audited annotation", method);
+
         // This can't be AUDIT'ed and CACHABLE'd at the same time as we only cache 'get'
         if (auditedAnnotation != null) {
             return invokeWithAuditAndHistory(auditedAnnotation, method, args);
@@ -213,6 +217,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
     private Object invokeWithAuditAndHistory(final Audited auditedAnnotation, final Method method, final Object[] args) throws Throwable {
         final InternalCallContext contextMaybeWithoutAccountRecordId = retrieveContextFromArguments(args);
         final List<String> entityIds = retrieveEntityIdsFromArguments(method, args);
+        Preconditions.checkState(!entityIds.isEmpty(), "@Audited Sql method must have entities (@Bind(\"id\")) as arguments");
         // We cannot always infer the TableName from the signature
         TableName tableName = retrieveTableNameFromArgumentsIfPossible(Arrays.asList(args));
         final ChangeType changeType = auditedAnnotation.value();
@@ -221,13 +226,10 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         // Get the current state before deletion for the history tables
         final Map<Long, M> deletedAndUpdatedEntities = new HashMap<Long, M>();
         if (changeType == ChangeType.DELETE) {
-            // TODO FIXME: this shouldn't happen (auditing for InvoiceTrackingSqlDao is broken)
-            if (!entityIds.isEmpty()) {
-                final List<M> entitiesToBeDeleted = sqlDao.getByIds(entityIds, contextMaybeWithoutAccountRecordId);
-                printSQLWarnings();
-                for (final M entityToBeDeleted : entitiesToBeDeleted) {
-                    deletedAndUpdatedEntities.put(entityToBeDeleted.getRecordId(), entityToBeDeleted);
-                }
+            final List<M> entitiesToBeDeleted = sqlDao.getByIds(entityIds, contextMaybeWithoutAccountRecordId);
+            printSQLWarnings();
+            for (final M entityToBeDeleted : entitiesToBeDeleted) {
+                deletedAndUpdatedEntities.put(entityToBeDeleted.getRecordId(), entityToBeDeleted);
             }
         }