killbill-memoizeit

cache: implement audit caches * Add caching for audit logs

5/3/2013 6:55:51 PM

Changes

util/src/main/java/com/ning/billing/util/cache/CacheControllerProvider.java 67(+0 -67)

Details

diff --git a/account/src/test/java/com/ning/billing/account/AccountTestSuiteWithEmbeddedDB.java b/account/src/test/java/com/ning/billing/account/AccountTestSuiteWithEmbeddedDB.java
index 74df794..f1dff1a 100644
--- a/account/src/test/java/com/ning/billing/account/AccountTestSuiteWithEmbeddedDB.java
+++ b/account/src/test/java/com/ning/billing/account/AccountTestSuiteWithEmbeddedDB.java
@@ -74,6 +74,7 @@ public abstract class AccountTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        controllerDispatcher.clearAll();
         bus.start();
     }
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 12bc382..457b626 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -85,6 +85,7 @@ import com.ning.billing.payment.api.TestPaymentMethodPluginBase;
 import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
 import com.ning.billing.util.api.RecordIdApi;
 import com.ning.billing.util.api.TagUserApi;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.config.OSGIConfig;
 import com.ning.billing.util.svcapi.account.AccountInternalApi;
 import com.ning.billing.util.svcapi.junction.BlockingInternalApi;
@@ -205,6 +206,9 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
     @Inject
     protected RecordIdApi recordIdApi;
 
+    @javax.inject.Inject
+    protected CacheControllerDispatcher controlCacheDispatcher;
+
     protected TestApiListener busHandler;
 
     private boolean isListenerFailed;
@@ -256,6 +260,8 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
         log.warn("\n");
         log.warn("RESET TEST FRAMEWORK\n\n");
 
+        controlCacheDispatcher.clearAll();
+
         clock.resetDeltaFromReality();
         resetTestListenerStatus();
         busHandler.reset();
diff --git a/invoice/src/test/java/com/ning/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java b/invoice/src/test/java/com/ning/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
index a1f3bfb..ef2d32f 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -121,6 +121,7 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        controllerDispatcher.clearAll();
         bus.start();
         restartInvoiceService(invoiceService);
     }
diff --git a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
index 7eeca11..9324e71 100644
--- a/overdue/src/test/java/com/ning/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
+++ b/overdue/src/test/java/com/ning/billing/overdue/OverdueTestSuiteWithEmbeddedDB.java
@@ -97,6 +97,7 @@ public abstract class OverdueTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        cacheControllerDispatcher.clearAll();
         bus.register(listener);
         bus.start();
 
diff --git a/server/src/main/resources/ehcache.xml b/server/src/main/resources/ehcache.xml
index ce3e135..07e2a73 100644
--- a/server/src/main/resources/ehcache.xml
+++ b/server/src/main/resources/ehcache.xml
@@ -17,17 +17,17 @@
   -->
 
 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="ehcache.xsd" >
+         xsi:noNamespaceSchemaLocation="ehcache.xsd">
 
-  <defaultCache
-      maxElementsInMemory="0"
-      maxElementsOnDisk="0"
-      eternal="false"
-      timeToIdleSeconds="0"
-      timeToLiveSeconds="0"
-      overflowToDisk="false"
-      statistics="true"
-      />
+    <defaultCache
+            maxElementsInMemory="0"
+            maxElementsOnDisk="0"
+            eternal="false"
+            timeToIdleSeconds="0"
+            timeToLiveSeconds="0"
+            overflowToDisk="false"
+            statistics="true"
+            />
 
     <cache name="record-id"
            maxElementsInMemory="100000"
@@ -35,10 +35,12 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
     </cache>
 
     <cache name="tenant-record-id"
@@ -47,10 +49,12 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
     </cache>
 
     <cache name="account-record-id"
@@ -59,10 +63,42 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
+    </cache>
+
+    <cache name="audit-log"
+           maxElementsInMemory="500000"
+           maxElementsOnDisk="0"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="600"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="com.ning.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+    <cache name="audit-log-via-history"
+           maxElementsInMemory="500000"
+           maxElementsOnDisk="0"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="600"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="com.ning.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
     </cache>
 </ehcache>
 
diff --git a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
index 62e7f91..900b676 100644
--- a/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
+++ b/server/src/test/java/com/ning/billing/jaxrs/TestJaxrsBase.java
@@ -58,6 +58,7 @@ import com.ning.billing.server.listeners.KillbillGuiceListener;
 import com.ning.billing.server.modules.KillbillServerModule;
 import com.ning.billing.tenant.glue.TenantModule;
 import com.ning.billing.usage.glue.UsageModule;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.config.PaymentConfig;
 import com.ning.billing.util.email.EmailModule;
 import com.ning.billing.util.email.templates.TemplateModule;
@@ -94,6 +95,9 @@ public class TestJaxrsBase extends KillbillClient {
     @Inject
     protected OSGIServiceRegistration<Servlet> servletRouter;
 
+    @Inject
+    protected CacheControllerDispatcher cacheControllerDispatcher;
+
     protected static TestKillbillGuiceListener listener;
 
     private HttpServer server;
@@ -219,6 +223,7 @@ public class TestJaxrsBase extends KillbillClient {
     @BeforeMethod(groups = "slow")
     public void beforeMethod() throws Exception {
         super.beforeMethod();
+        cacheControllerDispatcher.clearAll();
         busHandler.reset();
         clock.resetDeltaFromReality();
         clock.setDay(new LocalDate(2012, 8, 25));
diff --git a/server/src/test/java/com/ning/billing/server/security/TestKillbillJdbcRealm.java b/server/src/test/java/com/ning/billing/server/security/TestKillbillJdbcRealm.java
index 8f353f8..ac06a5a 100644
--- a/server/src/test/java/com/ning/billing/server/security/TestKillbillJdbcRealm.java
+++ b/server/src/test/java/com/ning/billing/server/security/TestKillbillJdbcRealm.java
@@ -33,7 +33,6 @@ import com.ning.billing.jaxrs.TestJaxrsBase;
 import com.ning.billing.tenant.api.DefaultTenant;
 import com.ning.billing.tenant.dao.DefaultTenantDao;
 import com.ning.billing.tenant.dao.TenantModelDao;
-import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.dao.DefaultNonEntityDao;
 
 import com.jolbox.bonecp.BoneCPConfig;
@@ -51,8 +50,7 @@ public class TestKillbillJdbcRealm extends TestJaxrsBase {
         super.beforeMethod();
 
         // Create the tenant
-        final CacheControllerDispatcher controllerDispatcher = new CacheControllerDispatcher();
-        final DefaultTenantDao tenantDao = new DefaultTenantDao(getDBI(), clock, controllerDispatcher, new DefaultNonEntityDao(getDBI()));
+        final DefaultTenantDao tenantDao = new DefaultTenantDao(getDBI(), clock, cacheControllerDispatcher, new DefaultNonEntityDao(getDBI()));
         tenant = new DefaultTenant(UUID.randomUUID(), null, null, UUID.randomUUID().toString(),
                                    UUID.randomUUID().toString(), UUID.randomUUID().toString());
         tenantDao.create(new TenantModelDao(tenant), internalCallContext);
diff --git a/util/src/main/java/com/ning/billing/util/audit/dao/DefaultAuditDao.java b/util/src/main/java/com/ning/billing/util/audit/dao/DefaultAuditDao.java
index afd769d..a4ae781 100644
--- a/util/src/main/java/com/ning/billing/util/audit/dao/DefaultAuditDao.java
+++ b/util/src/main/java/com/ning/billing/util/audit/dao/DefaultAuditDao.java
@@ -26,22 +26,28 @@ import org.skife.jdbi.v2.IDBI;
 import com.ning.billing.util.api.AuditLevel;
 import com.ning.billing.util.audit.AuditLog;
 import com.ning.billing.util.audit.ChangeType;
+import com.ning.billing.util.cache.CacheControllerDispatcher;
 import com.ning.billing.util.callcontext.InternalTenantContext;
-import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.clock.Clock;
+import com.ning.billing.util.dao.NonEntityDao;
 import com.ning.billing.util.dao.NonEntitySqlDao;
 import com.ning.billing.util.dao.TableName;
+import com.ning.billing.util.entity.dao.EntitySqlDao;
+import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
+import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
+import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
 
 import com.google.common.collect.ImmutableList;
 
 public class DefaultAuditDao implements AuditDao {
 
     private final NonEntitySqlDao nonEntitySqlDao;
-    private final AuditSqlDao auditSqlDao;
+    private final EntitySqlDaoTransactionalJdbiWrapper transactionalSqlDao;
 
     @Inject
-    public DefaultAuditDao(final IDBI dbi) {
+    public DefaultAuditDao(final IDBI dbi, final Clock clock, final CacheControllerDispatcher cacheControllerDispatcher, final NonEntityDao nonEntityDao) {
         this.nonEntitySqlDao = dbi.onDemand(NonEntitySqlDao.class);
-        this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
+        this.transactionalSqlDao = new EntitySqlDaoTransactionalJdbiWrapper(dbi, clock, cacheControllerDispatcher, nonEntityDao);
     }
 
     @Override
@@ -69,15 +75,27 @@ public class DefaultAuditDao implements AuditDao {
         }
 
         final Long targetRecordId = nonEntitySqlDao.getRecordIdFromObject(objectId.toString(), tableName.getTableName());
-        final List<AuditLog> allAuditLogs = auditSqlDao.getAuditLogsViaHistoryForTargetRecordId(historyTableName,
-                                                                                                historyTableName.getTableName().toLowerCase(),
-                                                                                                targetRecordId,
-                                                                                                context);
+        final List<AuditLog> allAuditLogs = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<AuditLog>>() {
+            @Override
+            public List<AuditLog> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(EntitySqlDao.class).getAuditLogsViaHistoryForTargetRecordId(historyTableName,
+                                                                                                                     historyTableName.getTableName().toLowerCase(),
+                                                                                                                     targetRecordId,
+                                                                                                                     context);
+            }
+        });
         return buildAuditLogs(auditLevel, allAuditLogs);
     }
 
     private List<AuditLog> getAuditLogsForRecordId(final TableName tableName, final Long targetRecordId, final AuditLevel auditLevel, final InternalTenantContext context) {
-        final List<AuditLog> allAuditLogs = auditSqlDao.getAuditLogsForTargetRecordId(tableName, targetRecordId, context);
+        final List<AuditLog> allAuditLogs = transactionalSqlDao.execute(new EntitySqlDaoTransactionWrapper<List<AuditLog>>() {
+            @Override
+            public List<AuditLog> inTransaction(final EntitySqlDaoWrapperFactory<EntitySqlDao> entitySqlDaoWrapperFactory) throws Exception {
+                return entitySqlDaoWrapperFactory.become(EntitySqlDao.class).getAuditLogsForTargetRecordId(tableName,
+                                                                                                           targetRecordId,
+                                                                                                           context);
+            }
+        });
         return buildAuditLogs(auditLevel, allAuditLogs);
     }
 
diff --git a/util/src/main/java/com/ning/billing/util/cache/AccountRecordIdCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/AccountRecordIdCacheLoader.java
index 9bc5a75..b4a044f 100644
--- a/util/src/main/java/com/ning/billing/util/cache/AccountRecordIdCacheLoader.java
+++ b/util/src/main/java/com/ning/billing/util/cache/AccountRecordIdCacheLoader.java
@@ -16,23 +16,20 @@
 
 package com.ning.billing.util.cache;
 
-import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
-import org.skife.jdbi.v2.Handle;
 import org.skife.jdbi.v2.IDBI;
-import org.skife.jdbi.v2.tweak.HandleCallback;
 
 import com.ning.billing.ObjectType;
 import com.ning.billing.util.cache.Cachable.CacheType;
 import com.ning.billing.util.dao.NonEntityDao;
-import com.ning.billing.util.dao.TableName;
 
 import net.sf.ehcache.loader.CacheLoader;
 
+@Singleton
 public class AccountRecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
 
     @Inject
@@ -41,22 +38,24 @@ public class AccountRecordIdCacheLoader extends BaseCacheLoader implements Cache
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
+    public CacheType getCacheType() {
+        return CacheType.ACCOUNT_RECORD_ID;
+    }
 
+    @Override
+    public Object load(final Object key, final Object argument) {
         checkCacheLoaderStatus();
 
-        if (!(argument instanceof ObjectType)) {
-            throw new IllegalArgumentException("Unexpected argument type of " +
-                                               argument != null ? argument.getClass().getName() : "null");
-        }
         if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " +
-                                               key != null ? key.getClass().getName() : "null");
-
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
         }
+
         final String objectId = (String) key;
-        final ObjectType objectType = (ObjectType) argument;
-        Long value = nonEntityDao.retrieveAccountRecordIdFromObject(UUID.fromString(objectId), objectType, null);
-        return value;
+        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
+
+        return nonEntityDao.retrieveAccountRecordIdFromObject(UUID.fromString(objectId), objectType, null);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/cache/AuditLogCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/AuditLogCacheLoader.java
new file mode 100644
index 0000000..810ab91
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/cache/AuditLogCacheLoader.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.cache;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.dao.TableName;
+
+import net.sf.ehcache.loader.CacheLoader;
+
+@Singleton
+public class AuditLogCacheLoader extends BaseCacheLoader implements CacheLoader {
+
+    private final AuditSqlDao auditSqlDao;
+
+    @Inject
+    public AuditLogCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+        super(dbi, nonEntityDao);
+        this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.AUDIT_LOG;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
+        }
+
+        final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+        final TableName tableName = (TableName) args[0];
+        final Long targetRecordId = (Long) args[1];
+        final InternalTenantContext internalTenantContext = (InternalTenantContext) args[2];
+
+        return auditSqlDao.getAuditLogsForTargetRecordId(tableName, targetRecordId, internalTenantContext);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/cache/AuditLogViaHistoryCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/AuditLogViaHistoryCacheLoader.java
new file mode 100644
index 0000000..91bb2fe
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/cache/AuditLogViaHistoryCacheLoader.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.cache;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.skife.jdbi.v2.IDBI;
+
+import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+import com.ning.billing.util.dao.AuditSqlDao;
+import com.ning.billing.util.dao.NonEntityDao;
+import com.ning.billing.util.dao.TableName;
+
+import net.sf.ehcache.loader.CacheLoader;
+
+@Singleton
+public class AuditLogViaHistoryCacheLoader extends BaseCacheLoader implements CacheLoader {
+
+    private final AuditSqlDao auditSqlDao;
+
+    @Inject
+    public AuditLogViaHistoryCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
+        super(dbi, nonEntityDao);
+        this.auditSqlDao = dbi.onDemand(AuditSqlDao.class);
+    }
+
+    @Override
+    public CacheType getCacheType() {
+        return CacheType.AUDIT_LOG_VIA_HISTORY;
+    }
+
+    @Override
+    public Object load(final Object key, final Object argument) {
+        checkCacheLoaderStatus();
+
+        if (!(key instanceof String)) {
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
+        }
+
+        final Object[] args = ((CacheLoaderArgument) argument).getArgs();
+        final TableName tableName = (TableName) args[0];
+        final String historyTableName = (String) args[1];
+        final Long targetRecordId = (Long) args[2];
+        final InternalTenantContext internalTenantContext = (InternalTenantContext) args[3];
+
+        return auditSqlDao.getAuditLogsViaHistoryForTargetRecordId(tableName, historyTableName, targetRecordId, internalTenantContext);
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/cache/BaseCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/BaseCacheLoader.java
index 855fdef..84ff464 100644
--- a/util/src/main/java/com/ning/billing/util/cache/BaseCacheLoader.java
+++ b/util/src/main/java/com/ning/billing/util/cache/BaseCacheLoader.java
@@ -23,6 +23,7 @@ import javax.inject.Inject;
 
 import org.skife.jdbi.v2.IDBI;
 
+import com.ning.billing.util.cache.Cachable.CacheType;
 import com.ning.billing.util.dao.NonEntityDao;
 
 import net.sf.ehcache.CacheException;
@@ -44,10 +45,11 @@ public abstract class BaseCacheLoader implements CacheLoader {
         this.cacheLoaderStatus = Status.STATUS_UNINITIALISED;
     }
 
+    public abstract CacheType getCacheType();
+
     @Override
     public abstract Object load(final Object key, final Object argument);
 
-
     @Override
     public Object load(final Object key) throws CacheException {
         throw new IllegalStateException("Method load is not implemented ");
diff --git a/util/src/main/java/com/ning/billing/util/cache/Cachable.java b/util/src/main/java/com/ning/billing/util/cache/Cachable.java
index ac9e0ee..cdd65cc 100644
--- a/util/src/main/java/com/ning/billing/util/cache/Cachable.java
+++ b/util/src/main/java/com/ning/billing/util/cache/Cachable.java
@@ -20,7 +20,6 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-import java.util.UUID;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Target({ElementType.METHOD})
@@ -29,42 +28,41 @@ public @interface Cachable {
     public final String RECORD_ID_CACHE_NAME = "record-id";
     public final String ACCOUNT_RECORD_ID_CACHE_NAME = "account-record-id";
     public final String TENANT_RECORD_ID_CACHE_NAME = "tenant-record-id";
+    public final String AUDIT_LOG_CACHE_NAME = "audit-log";
+    public final String AUDIT_LOG_VIA_HISTORY_CACHE_NAME = "audit-log-via-history";
 
     public CacheType value();
 
     public enum CacheType {
         /* Mapping from object 'id (UUID)' -> object 'recordId (Long' */
-        RECORD_ID(RECORD_ID_CACHE_NAME, UUID.class, Long.class),
+        RECORD_ID(RECORD_ID_CACHE_NAME),
 
         /* Mapping from object 'id (UUID)' -> matching account object 'accountRecordId (Long)' */
-        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME, UUID.class, Long.class),
-
+        ACCOUNT_RECORD_ID(ACCOUNT_RECORD_ID_CACHE_NAME),
 
         /* Mapping from object 'id (UUID)' -> matching object 'tenantRecordId (Long)' */
-        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME, UUID.class, Long.class);
+        TENANT_RECORD_ID(TENANT_RECORD_ID_CACHE_NAME),
+
+        /* Mapping from object 'tableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
+        AUDIT_LOG(AUDIT_LOG_CACHE_NAME),
+
+        /* Mapping from object 'tableName::historyTableName::targetRecordId' -> matching objects 'Iterable<AuditLog>' */
+        AUDIT_LOG_VIA_HISTORY(AUDIT_LOG_VIA_HISTORY_CACHE_NAME);
 
         private final String cacheName;
-        private final Class key;
-        private final Class value;
 
-        CacheType(final String cacheName, final Class key, final Class value) {
+        CacheType(final String cacheName) {
             this.cacheName = cacheName;
-            this.key = key;
-            this.value = value;
-        }
-
-        public Class getKey() {
-            return key;
         }
 
-        public Class getValue() {
-            return value;
+        public String getCacheName() {
+            return cacheName;
         }
 
         public static CacheType findByName(final String input) {
-            for (CacheType cur : CacheType.values()) {
-                if (cur.cacheName.equals(input)) {
-                    return cur;
+            for (final CacheType cacheType : CacheType.values()) {
+                if (cacheType.cacheName.equals(input)) {
+                    return cacheType;
                 }
             }
             return null;
diff --git a/util/src/main/java/com/ning/billing/util/cache/CachableKey.java b/util/src/main/java/com/ning/billing/util/cache/CachableKey.java
new file mode 100644
index 0000000..1ef1f50
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/cache/CachableKey.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.cache;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER})
+public @interface CachableKey {
+
+    // Position (start at 1)
+    public int value();
+}
diff --git a/util/src/main/java/com/ning/billing/util/cache/CacheController.java b/util/src/main/java/com/ning/billing/util/cache/CacheController.java
index 4655a8c..581dd4b 100644
--- a/util/src/main/java/com/ning/billing/util/cache/CacheController.java
+++ b/util/src/main/java/com/ning/billing/util/cache/CacheController.java
@@ -16,18 +16,13 @@
 
 package com.ning.billing.util.cache;
 
-import java.util.Collection;
-
-import com.ning.billing.ObjectType;
-import com.ning.billing.util.cache.Cachable.CacheType;
-
 public interface CacheController<K, V> {
 
-    public CacheType getType();
-
-    public V get(K key, ObjectType objectType);
+    public V get(K key, CacheLoaderArgument objectType);
 
     public boolean remove(K key);
 
     public int size();
+
+    void removeAll();
 }
diff --git a/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcher.java b/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcher.java
index ee9b3d0..8681220 100644
--- a/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcher.java
+++ b/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcher.java
@@ -16,38 +16,35 @@
 
 package com.ning.billing.util.cache;
 
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.UUID;
 
 import javax.inject.Inject;
 
 import com.ning.billing.util.cache.Cachable.CacheType;
 
-import com.google.inject.name.Named;
-
+// Kill Bill generic cache dispatcher
 public class CacheControllerDispatcher {
 
-    private final Map<CacheType, CacheController<?,?>> caches;
+    private final Map<CacheType, CacheController<Object, Object>> caches;
 
     @Inject
-    public CacheControllerDispatcher(@Named(Cachable.RECORD_ID_CACHE_NAME) final CacheController<UUID, Long> recordIdCacheController,
-                                     @Named(Cachable.ACCOUNT_RECORD_ID_CACHE_NAME) final CacheController<UUID, Long> accountRecordIdCacheController,
-                                     @Named(Cachable.TENANT_RECORD_ID_CACHE_NAME) final CacheController<UUID, Long> tenantRecordIdCacheController) {
-        caches = new HashMap<CacheType, CacheController<?, ?>>();
-        caches.put(recordIdCacheController.getType(), recordIdCacheController);
-        caches.put(accountRecordIdCacheController.getType(), accountRecordIdCacheController);
-        caches.put(tenantRecordIdCacheController.getType(), tenantRecordIdCacheController);
+    public CacheControllerDispatcher(final Map<CacheType, CacheController<Object, Object>> caches) {
+        this.caches = caches;
     }
 
     // Test only
     public CacheControllerDispatcher() {
-        caches = new HashMap<CacheType, CacheController<?, ?>>();
+        caches = new HashMap<CacheType, CacheController<Object, Object>>();
+    }
+
+    public CacheController<Object, Object> getCacheController(final CacheType cacheType) {
+        return caches.get(cacheType);
     }
 
-    public <K,V> CacheController<K, V> getCacheController(CacheType cacheType) {
-        // STEPH Not the prettiest thing..
-        return  CacheController.class.cast(caches.get(cacheType));
+    public void clearAll() {
+        for (final CacheController<Object, Object> cacheController : caches.values()) {
+            cacheController.removeAll();
+        }
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcherProvider.java b/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcherProvider.java
new file mode 100644
index 0000000..79a9894
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/cache/CacheControllerDispatcherProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.cache;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import com.ning.billing.util.cache.Cachable.CacheType;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Collections2;
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.loader.CacheLoader;
+
+// Build the abstraction layer between EhCache and Kill Bill
+public class CacheControllerDispatcherProvider implements Provider<CacheControllerDispatcher> {
+
+    private final CacheManager cacheManager;
+
+    @Inject
+    public CacheControllerDispatcherProvider(final CacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+    }
+
+    @Override
+    public CacheControllerDispatcher get() {
+        final Map<CacheType, CacheController<Object, Object>> cacheControllers = new LinkedHashMap<CacheType, CacheController<Object, Object>>();
+        for (final String cacheName : cacheManager.getCacheNames()) {
+            final CacheType cacheType = CacheType.findByName(cacheName);
+
+            final Collection<EhCacheBasedCacheController<Object, Object>> cacheControllersForCacheName = getCacheControllersForCacheName(cacheName);
+            // EhCache supports multiple cache loaders per type, but not Kill Bill - take the first one
+            if (cacheControllersForCacheName.size() > 0) {
+                final EhCacheBasedCacheController<Object, Object> ehCacheBasedCacheController = cacheControllersForCacheName.iterator().next();
+                cacheControllers.put(cacheType, ehCacheBasedCacheController);
+            }
+        }
+
+        return new CacheControllerDispatcher(cacheControllers);
+    }
+
+    public Collection<EhCacheBasedCacheController<Object, Object>> getCacheControllersForCacheName(final String name) {
+        final Cache cache = cacheManager.getCache(name);
+
+        // The CacheLoaders were registered in EhCacheCacheManagerProvider
+        return Collections2.transform(cache.getRegisteredCacheLoaders(), new Function<CacheLoader, EhCacheBasedCacheController<Object, Object>>() {
+            @Override
+            public EhCacheBasedCacheController<Object, Object> apply(final CacheLoader input) {
+                return new EhCacheBasedCacheController<Object, Object>(cache);
+            }
+        });
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/cache/CacheLoaderArgument.java b/util/src/main/java/com/ning/billing/util/cache/CacheLoaderArgument.java
new file mode 100644
index 0000000..11f51a0
--- /dev/null
+++ b/util/src/main/java/com/ning/billing/util/cache/CacheLoaderArgument.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010-2013 Ning, Inc.
+ *
+ * Ning 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:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.util.cache;
+
+import javax.annotation.Nullable;
+
+import com.ning.billing.ObjectType;
+import com.ning.billing.util.callcontext.InternalTenantContext;
+
+public class CacheLoaderArgument {
+
+    private final ObjectType objectType;
+    private final Object[] args;
+    private final InternalTenantContext internalTenantContext;
+
+    public CacheLoaderArgument(final ObjectType objectType) {
+        this(objectType, new Object[]{}, null);
+    }
+
+    public CacheLoaderArgument(final ObjectType objectType, final Object[] args, @Nullable final InternalTenantContext internalTenantContext) {
+        this.objectType = objectType;
+        this.args = args;
+        this.internalTenantContext = internalTenantContext;
+    }
+
+    public ObjectType getObjectType() {
+        return objectType;
+    }
+
+    public Object[] getArgs() {
+        return args;
+    }
+
+    public InternalTenantContext getInternalTenantContext() {
+        return internalTenantContext;
+    }
+}
diff --git a/util/src/main/java/com/ning/billing/util/cache/EhCacheBasedCacheController.java b/util/src/main/java/com/ning/billing/util/cache/EhCacheBasedCacheController.java
index 623f1f7..6f0a5b3 100644
--- a/util/src/main/java/com/ning/billing/util/cache/EhCacheBasedCacheController.java
+++ b/util/src/main/java/com/ning/billing/util/cache/EhCacheBasedCacheController.java
@@ -16,33 +16,20 @@
 
 package com.ning.billing.util.cache;
 
-import java.util.Collection;
-import java.util.Map;
-
-import com.ning.billing.ObjectType;
-import com.ning.billing.util.cache.Cachable.CacheType;
-
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.Element;
 
 public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> {
 
     private final Cache cache;
-    private final CacheType cacheType;
 
-    public EhCacheBasedCacheController(final Cache cache, final CacheType cacheType) {
+    public EhCacheBasedCacheController(final Cache cache) {
         this.cache = cache;
-        this.cacheType = cacheType;
-    }
-
-    @Override
-    public CacheType getType() {
-        return cacheType;
     }
 
     @Override
-    public V get(final K key, final ObjectType objectType) {
-        final Element element = cache.getWithLoader(key, null, objectType);
+    public V get(final K key, final CacheLoaderArgument cacheLoaderArgument) {
+        final Element element = cache.getWithLoader(key, null, cacheLoaderArgument);
         if (element == null) {
             return null;
         }
@@ -58,4 +45,9 @@ public class EhCacheBasedCacheController<K, V> implements CacheController<K, V> 
     public int size() {
         return cache.getSize();
     }
+
+    @Override
+    public void removeAll() {
+        cache.removeAll();
+    }
 }
diff --git a/util/src/main/java/com/ning/billing/util/cache/RecordIdCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/RecordIdCacheLoader.java
index 3489753..fe9fc1f 100644
--- a/util/src/main/java/com/ning/billing/util/cache/RecordIdCacheLoader.java
+++ b/util/src/main/java/com/ning/billing/util/cache/RecordIdCacheLoader.java
@@ -19,15 +19,18 @@ package com.ning.billing.util.cache;
 import java.util.UUID;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.ObjectType;
+import com.ning.billing.util.cache.Cachable.CacheType;
 import com.ning.billing.util.dao.NonEntityDao;
 
 import net.sf.ehcache.CacheException;
 import net.sf.ehcache.loader.CacheLoader;
 
+@Singleton
 public class RecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
 
     @Inject
@@ -36,22 +39,24 @@ public class RecordIdCacheLoader extends BaseCacheLoader implements CacheLoader 
     }
 
     @Override
-    public Object load(final Object key, final Object argument) throws CacheException {
+    public CacheType getCacheType() {
+        return CacheType.RECORD_ID;
+    }
 
+    @Override
+    public Object load(final Object key, final Object argument) throws CacheException {
         checkCacheLoaderStatus();
 
-        if (!(argument instanceof ObjectType)) {
-            throw new IllegalArgumentException("Unexpected argument type of " +
-                                               argument != null ? argument.getClass().getName() : "null");
-        }
         if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " +
-                                               key != null ? key.getClass().getName() : "null");
-
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
         }
+
         final String objectId = (String) key;
-        final ObjectType objectType = (ObjectType) argument;
-        Long value = nonEntityDao.retrieveRecordIdFromObject(UUID.fromString(objectId), objectType, null);
-        return value;
+        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
+
+        return nonEntityDao.retrieveRecordIdFromObject(UUID.fromString(objectId), objectType, null);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/cache/TenantRecordIdCacheLoader.java b/util/src/main/java/com/ning/billing/util/cache/TenantRecordIdCacheLoader.java
index d8c2eb4..c16d4de 100644
--- a/util/src/main/java/com/ning/billing/util/cache/TenantRecordIdCacheLoader.java
+++ b/util/src/main/java/com/ning/billing/util/cache/TenantRecordIdCacheLoader.java
@@ -19,15 +19,18 @@ package com.ning.billing.util.cache;
 import java.util.UUID;
 
 import javax.inject.Inject;
+import javax.inject.Singleton;
 
 import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.ObjectType;
+import com.ning.billing.util.cache.Cachable.CacheType;
 import com.ning.billing.util.dao.NonEntityDao;
 
 import net.sf.ehcache.loader.CacheLoader;
 
-public class TenantRecordIdCacheLoader  extends BaseCacheLoader implements CacheLoader {
+@Singleton
+public class TenantRecordIdCacheLoader extends BaseCacheLoader implements CacheLoader {
 
     @Inject
     public TenantRecordIdCacheLoader(final IDBI dbi, final NonEntityDao nonEntityDao) {
@@ -35,22 +38,24 @@ public class TenantRecordIdCacheLoader  extends BaseCacheLoader implements Cache
     }
 
     @Override
-    public Object load(final Object key, final Object argument) {
+    public CacheType getCacheType() {
+        return CacheType.TENANT_RECORD_ID;
+    }
 
+    @Override
+    public Object load(final Object key, final Object argument) {
         checkCacheLoaderStatus();
 
-        if (!(argument instanceof ObjectType)) {
-            throw new IllegalArgumentException("Unexpected argument type of " +
-                                               argument != null ? argument.getClass().getName() : "null");
-        }
         if (!(key instanceof String)) {
-            throw new IllegalArgumentException("Unexpected key type of " +
-                                               key != null ? key.getClass().getName() : "null");
-
+            throw new IllegalArgumentException("Unexpected key type of " + key.getClass().getName());
+        }
+        if (!(argument instanceof CacheLoaderArgument)) {
+            throw new IllegalArgumentException("Unexpected key type of " + argument.getClass().getName());
         }
+
         final String objectId = (String) key;
-        final ObjectType objectType = (ObjectType) argument;
-        Long value = nonEntityDao.retrieveTenantRecordIdFromObject(UUID.fromString(objectId), objectType, null);
-        return value;
+        final ObjectType objectType = ((CacheLoaderArgument) argument).getObjectType();
+
+        return nonEntityDao.retrieveTenantRecordIdFromObject(UUID.fromString(objectId), objectType, null);
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java b/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
index 3b95a3b..fea7437 100644
--- a/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/dao/AuditSqlDao.java
@@ -20,17 +20,26 @@ import java.util.List;
 
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.BindBean;
-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.Define;
 import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;
 
 import com.ning.billing.util.audit.AuditLog;
+import com.ning.billing.util.cache.Cachable;
+import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.cache.CachableKey;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.entity.dao.EntitySqlDaoStringTemplate;
 
+/**
+ * Note: cache invalidation has to happen for audit logs (which is tricky in the multi-nodes scenario).
+ * For now, we're using a time-based eviction strategy (see timeToIdleSeconds and timeToLiveSeconds in ehcache.xml)
+ * which is good enough: the cache will always get at least the initial CREATION audit log entry, which is the one
+ * we really care about (both for Analytics and for Kaui's endpoints). Besides, we do cache invalidation properly
+ * on our own node (see EntitySqlDaoWrapperInvocationHandler).
+ */
 @EntitySqlDaoStringTemplate("/com/ning/billing/util/entity/dao/EntitySqlDao.sql.stg")
 @RegisterMapper(AuditLogMapper.class)
 public interface AuditSqlDao {
@@ -39,18 +48,16 @@ public interface AuditSqlDao {
     public void insertAuditFromTransaction(@BindBean final EntityAudit audit,
                                            @BindBean final InternalCallContext context);
 
-    @SqlBatch(transactional = false)
-    public void insertAuditFromTransaction(@BindBean final List<EntityAudit> audit,
-                                           @BindBean final InternalCallContext context);
-
     @SqlQuery
-    public List<AuditLog> getAuditLogsForTargetRecordId(@BindBean final TableName tableName,
-                                                        @Bind("targetRecordId") final long targetRecordId,
+    @Cachable(CacheType.AUDIT_LOG)
+    public List<AuditLog> getAuditLogsForTargetRecordId(@CachableKey(1) @BindBean final TableName tableName,
+                                                        @CachableKey(2) @Bind("targetRecordId") final long targetRecordId,
                                                         @BindBean final InternalTenantContext context);
 
     @SqlQuery
-    public List<AuditLog> getAuditLogsViaHistoryForTargetRecordId(@BindBean final TableName tableName,
-                                                                  @Define("historyTableName") final String historyTableName,
-                                                                  @Bind("targetRecordId") final long targetRecordId,
+    @Cachable(CacheType.AUDIT_LOG_VIA_HISTORY)
+    public List<AuditLog> getAuditLogsViaHistoryForTargetRecordId(@CachableKey(1) @BindBean final TableName historyTableName, /* Uppercased - used to find entries in audit_log table */
+                                                                  @CachableKey(2) @Define("historyTableName") final String actualHistoryTableName, /* Actual table name, used in the inner join query */
+                                                                  @CachableKey(3) @Bind("targetRecordId") final long targetRecordId,
                                                                   @BindBean final InternalTenantContext context);
 }
diff --git a/util/src/main/java/com/ning/billing/util/dao/DefaultNonEntityDao.java b/util/src/main/java/com/ning/billing/util/dao/DefaultNonEntityDao.java
index dbf838a..232983e 100644
--- a/util/src/main/java/com/ning/billing/util/dao/DefaultNonEntityDao.java
+++ b/util/src/main/java/com/ning/billing/util/dao/DefaultNonEntityDao.java
@@ -25,6 +25,7 @@ import org.skife.jdbi.v2.IDBI;
 
 import com.ning.billing.ObjectType;
 import com.ning.billing.util.cache.CacheController;
+import com.ning.billing.util.cache.CacheLoaderArgument;
 
 public class DefaultNonEntityDao implements NonEntityDao {
 
@@ -52,8 +53,6 @@ public class DefaultNonEntityDao implements NonEntityDao {
     }
 
     public Long retrieveAccountRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
-
-
         return containedCall.withCaching(new OperationRetrieval<Long>() {
             @Override
             public Long doRetrieve(final UUID objectId, final ObjectType objectType) {
@@ -74,7 +73,6 @@ public class DefaultNonEntityDao implements NonEntityDao {
         }, objectId, objectType, cache);
     }
 
-
     public Long retrieveTenantRecordIdFromObject(@Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
 
 
@@ -110,18 +108,16 @@ public class DefaultNonEntityDao implements NonEntityDao {
         public T doRetrieve(final UUID objectId, final ObjectType objectType);
     }
 
-
     // 'cache' will be null for the CacheLoader classes -- or if cache is not configured.
     private class WithCaching {
-        private Long withCaching(final OperationRetrieval<Long> op, @Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
 
+        private Long withCaching(final OperationRetrieval<Long> op, @Nullable final UUID objectId, final ObjectType objectType, @Nullable final CacheController<Object, Object> cache) {
             if (objectId == null) {
                 return null;
             }
 
             if (cache != null) {
-                final Long cachedResult = (Long) cache.get(objectId.toString(), objectType);
-                return cachedResult;
+                return (Long) cache.get(objectId.toString(), new CacheLoaderArgument(objectType));
             }
             return op.doRetrieve(objectId, objectType);
         }
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
index 25f8e6c..14f3859 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDao.java
@@ -29,6 +29,7 @@ import org.skife.jdbi.v2.sqlobject.mixins.Transmogrifier;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.cache.Cachable;
 import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.cache.CachableKey;
 import com.ning.billing.util.callcontext.InternalCallContext;
 import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.dao.AuditSqlDao;
@@ -61,7 +62,7 @@ public interface EntitySqlDao<M extends EntityModelDao<E>, E extends Entity> ext
 
     @SqlQuery
     @Cachable(CacheType.RECORD_ID)
-    public Long getRecordId(@Bind("id") final String id,
+    public Long getRecordId(@CachableKey(1) @Bind("id") final String id,
                             @BindBean final InternalTenantContext context);
 
     @SqlQuery
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoStringTemplate.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
index f96ea42..143f3bc 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoStringTemplate.java
@@ -23,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 import java.lang.reflect.Method;
 import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 
 import org.skife.jdbi.v2.Query;
 import org.skife.jdbi.v2.SQLStatement;
@@ -75,7 +76,8 @@ public @interface EntitySqlDaoStringTemplate {
                                 if (sqlObjectType.getGenericInterfaces()[i] instanceof ParameterizedType) {
                                     final ParameterizedType type = (ParameterizedType) sqlObjectType.getGenericInterfaces()[i];
                                     for (int j = 0; j < type.getActualTypeArguments().length; j++) {
-                                        final Class modelClazz = (Class) type.getActualTypeArguments()[j];
+                                        final Type modelType = type.getActualTypeArguments()[j];
+                                        final Class modelClazz = (Class) modelType;
                                         if (Entity.class.isAssignableFrom(modelClazz)) {
                                             query.registerMapper(new LowerToCamelBeanMapperFactory(modelClazz));
                                         }
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index 0cd07c6..a00d894 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -25,9 +25,9 @@ import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.UUID;
 
 import org.skife.jdbi.v2.Binding;
 import org.skife.jdbi.v2.StatementContext;
@@ -41,9 +41,12 @@ import com.ning.billing.ObjectType;
 import com.ning.billing.util.audit.ChangeType;
 import com.ning.billing.util.cache.Cachable;
 import com.ning.billing.util.cache.Cachable.CacheType;
+import com.ning.billing.util.cache.CachableKey;
 import com.ning.billing.util.cache.CacheController;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
+import com.ning.billing.util.cache.CacheLoaderArgument;
 import com.ning.billing.util.callcontext.InternalCallContext;
+import com.ning.billing.util.callcontext.InternalTenantContext;
 import com.ning.billing.util.clock.Clock;
 import com.ning.billing.util.dao.EntityAudit;
 import com.ning.billing.util.dao.EntityHistoryModelDao;
@@ -51,11 +54,13 @@ import com.ning.billing.util.dao.NonEntityDao;
 import com.ning.billing.util.dao.NonEntitySqlDao;
 import com.ning.billing.util.dao.TableName;
 import com.ning.billing.util.entity.Entity;
-import com.ning.billing.util.tag.dao.TagSqlDao;
 
 import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 
 /**
  * Wraps an instance of EntitySqlDao, performing extra work around each method (Sql query)
@@ -66,6 +71,8 @@ import com.google.common.collect.ImmutableList.Builder;
  */
 public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, M extends EntityModelDao<E>, E extends Entity> implements InvocationHandler {
 
+    public static final String CACHE_KEY_SEPARATOR = "::";
+
     private final Logger logger = LoggerFactory.getLogger(EntitySqlDaoWrapperInvocationHandler.class);
 
     private final Class<S> sqlDaoClass;
@@ -169,14 +176,36 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
 
     private Object invokeWithCaching(final Cachable cachableAnnotation, final Method method, final Object[] args)
             throws IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException {
-
         final ObjectType objectType = getObjectType();
         final CacheType cacheType = cachableAnnotation.value();
         final CacheController<Object, Object> cache = cacheControllerDispatcher.getCacheController(cacheType);
         Object result = null;
         if (cache != null) {
-            // STEPH : Assume first argument is the key for the cache, this is a bit fragile...
-            result = cache.get(args[0], objectType);
+            // Find all arguments marked with @CachableKey
+            final Map<Integer, Object> keyPieces = new LinkedHashMap<Integer, Object>();
+            final Annotation[][] annotations = method.getParameterAnnotations();
+            for (int i = 0; i < annotations.length; i++) {
+                for (int j = 0; j < annotations[i].length; j++) {
+                    final Annotation annotation = annotations[i][j];
+                    if (CachableKey.class.equals(annotation.annotationType())) {
+                        // CachableKey position starts at 1
+                        keyPieces.put(((CachableKey) annotation).value() - 1, args[i]);
+                        break;
+                    }
+                }
+            }
+
+            // Build the Cache key
+            final String cacheKey = buildCacheKey(keyPieces);
+
+            final InternalTenantContext internalTenantContext = (InternalTenantContext) Iterables.find(ImmutableList.copyOf(args), new Predicate<Object>() {
+                @Override
+                public boolean apply(final Object input) {
+                    return input instanceof InternalTenantContext;
+                }
+            }, null);
+            final CacheLoaderArgument cacheLoaderArgument = new CacheLoaderArgument(objectType, args, internalTenantContext);
+            result = cache.get(cacheKey, cacheLoaderArgument);
         }
         if (result == null) {
             result = method.invoke(sqlDao, args);
@@ -198,17 +227,23 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         int foundIndexForEntitySqlDao = -1;
         // If the sqlDaoClass implements multiple interfaces, first figure out which one is the EntitySqlDao
         for (int i = 0; i < sqlDaoClass.getGenericInterfaces().length; i++) {
-            if (EntitySqlDao.class.getName().equals(((Class) ((java.lang.reflect.ParameterizedType) sqlDaoClass.getGenericInterfaces()[0]).getRawType()).getName())) {
+            final Type type = sqlDaoClass.getGenericInterfaces()[0];
+            if (!(type instanceof java.lang.reflect.ParameterizedType)) {
+                // AuditSqlDao for example won't extend EntitySqlDao
+                return null;
+            }
+
+            if (EntitySqlDao.class.getName().equals(((Class) ((java.lang.reflect.ParameterizedType) type).getRawType()).getName())) {
                 foundIndexForEntitySqlDao = i;
                 break;
             }
         }
         // Find out from the parameters of the EntitySqlDao which one is the EntityModelDao, and extract his (sub)type to finally return the ObjectType
         if (foundIndexForEntitySqlDao >= 0) {
-            Type[] types = ((java.lang.reflect.ParameterizedType) sqlDaoClass.getGenericInterfaces()[foundIndexForEntitySqlDao]).getActualTypeArguments();
+            final Type[] types = ((java.lang.reflect.ParameterizedType) sqlDaoClass.getGenericInterfaces()[foundIndexForEntitySqlDao]).getActualTypeArguments();
             int foundIndexForEntityModelDao = -1;
             for (int i = 0; i < types.length; i++) {
-                Class clz = ((Class) types[i]);
+                final Class clz = ((Class) types[i]);
                 if (EntityModelDao.class.getName().equals(((Class) ((java.lang.reflect.ParameterizedType) clz.getGenericInterfaces()[0]).getRawType()).getName())) {
                     foundIndexForEntityModelDao = i;
                     break;
@@ -216,11 +251,11 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
             }
 
             if (foundIndexForEntityModelDao >= 0) {
-                String modelClassName = ((Class) types[foundIndexForEntityModelDao]).getName();
+                final String modelClassName = ((Class) types[foundIndexForEntityModelDao]).getName();
 
-                Class<? extends EntityModelDao<?>> clz = (Class<? extends EntityModelDao<?>>) Class.forName(modelClassName);
+                final Class<? extends EntityModelDao<?>> clz = (Class<? extends EntityModelDao<?>>) Class.forName(modelClassName);
 
-                EntityModelDao<?> modelDao = (EntityModelDao<?>) clz.newInstance();
+                final EntityModelDao<?> modelDao = (EntityModelDao<?>) clz.newInstance();
                 return modelDao.getTableName().getObjectType();
             }
         }
@@ -272,7 +307,7 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
             historyRecordId = entityRecordId;
         }
 
-        insertAudits(tableName, historyRecordId, changeType, context);
+        insertAudits(tableName, entityRecordId, historyRecordId, changeType, context);
     }
 
     private List<String> retrieveEntityIdsFromArguments(final Method method, final Object[] args) {
@@ -349,9 +384,34 @@ public class EntitySqlDaoWrapperInvocationHandler<S extends EntitySqlDao<M, E>, 
         return nonEntityDao.retrieveLastHistoryRecordIdFromTransaction(entityRecordId, entityModelDao.getHistoryTableName(), transactional);
     }
 
-    private void insertAudits(final TableName tableName, final Long historyRecordId, final ChangeType changeType, final InternalCallContext context) {
+    private void insertAudits(final TableName tableName, final Long entityRecordId, final Long historyRecordId, final ChangeType changeType, final InternalCallContext context) {
         final TableName destinationTableName = Objects.firstNonNull(tableName.getHistoryTableName(), tableName);
         final EntityAudit audit = new EntityAudit(destinationTableName, historyRecordId, changeType, clock.getUTCNow());
         sqlDao.insertAuditFromTransaction(audit, context);
+
+        // We need to invalidate the caches. There is a small window of doom here where caches will be stale.
+        // TODO Knowledge on how the key is constructed is also in AuditSqlDao
+        if (tableName.getHistoryTableName() != null) {
+            final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName.getHistoryTableName(), 1, tableName.getHistoryTableName(), 2, entityRecordId));
+            cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG_VIA_HISTORY).remove(key);
+        } else {
+            final String key = buildCacheKey(ImmutableMap.<Integer, Object>of(0, tableName, 1, entityRecordId));
+            cacheControllerDispatcher.getCacheController(CacheType.AUDIT_LOG).remove(key);
+        }
+    }
+
+    private String buildCacheKey(final Map<Integer, Object> keyPieces) {
+        final StringBuilder cacheKey = new StringBuilder();
+        for (int i = 0; i < keyPieces.size(); i++) {
+            // To normalize the arguments and avoid casing issues, we make all pieces of the key uppercase.
+            // Since the database engine may be case insensitive and we use arguments of the SQL method call
+            // to build the key, the key has to be case insensitive as well.
+            final String str = String.valueOf(keyPieces.get(i)).toUpperCase();
+            cacheKey.append(str);
+            if (i < keyPieces.size() - 1) {
+                cacheKey.append(CACHE_KEY_SEPARATOR);
+            }
+        }
+        return cacheKey.toString();
     }
 }
diff --git a/util/src/main/java/com/ning/billing/util/glue/CacheModule.java b/util/src/main/java/com/ning/billing/util/glue/CacheModule.java
index df3c014..f8e3ec1 100644
--- a/util/src/main/java/com/ning/billing/util/glue/CacheModule.java
+++ b/util/src/main/java/com/ning/billing/util/glue/CacheModule.java
@@ -16,60 +16,34 @@
 
 package com.ning.billing.util.glue;
 
-import java.util.UUID;
-
 import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 
-import com.ning.billing.util.cache.AccountRecordIdCacheLoader;
-import com.ning.billing.util.cache.Cachable;
-import com.ning.billing.util.cache.CacheController;
 import com.ning.billing.util.cache.CacheControllerDispatcher;
-import com.ning.billing.util.cache.CacheControllerProvider;
-import com.ning.billing.util.cache.CacheManagerProvider;
-import com.ning.billing.util.cache.RecordIdCacheLoader;
-import com.ning.billing.util.cache.TenantRecordIdCacheLoader;
+import com.ning.billing.util.cache.CacheControllerDispatcherProvider;
+import com.ning.billing.util.cache.EhCacheCacheManagerProvider;
 import com.ning.billing.util.config.CacheConfig;
 
 import com.google.inject.AbstractModule;
-import com.google.inject.TypeLiteral;
-import com.google.inject.name.Named;
-import com.google.inject.name.Names;
 import net.sf.ehcache.CacheManager;
-import net.sf.ehcache.loader.CacheLoader;
 
 public class CacheModule extends AbstractModule {
 
-    public static final Named RECORD_ID_CACHE_NAMED = Names.named(Cachable.RECORD_ID_CACHE_NAME);
-    public static final Named ACCOUNT_RECORD_ID_CACHE_NAMED = Names.named(Cachable.ACCOUNT_RECORD_ID_CACHE_NAME);
-    public static final Named TENANT_RECORD_ID_CACHE_NAMED = Names.named(Cachable.TENANT_RECORD_ID_CACHE_NAME);
-
     private final ConfigSource configSource;
 
     public CacheModule(final ConfigSource configSource) {
         this.configSource = configSource;
     }
 
-    protected void installConfig() {
-        final CacheConfig config = new ConfigurationObjectFactory(configSource).build(CacheConfig.class);
-        bind(CacheConfig.class).toInstance(config);
-    }
-
     @Override
     protected void configure() {
-        installConfig();
-
-        bind(CacheManager.class).toProvider(CacheManagerProvider.class).asEagerSingleton();
-
-        bind(CacheLoader.class).annotatedWith(RECORD_ID_CACHE_NAMED).to(RecordIdCacheLoader.class).asEagerSingleton();
-        bind(new TypeLiteral<CacheController<UUID, Long>>() {}).annotatedWith(RECORD_ID_CACHE_NAMED).toProvider(new CacheControllerProvider<UUID, Long>(Cachable.RECORD_ID_CACHE_NAME)).asEagerSingleton();
-
-        bind(CacheLoader.class).annotatedWith(ACCOUNT_RECORD_ID_CACHE_NAMED).to(AccountRecordIdCacheLoader.class).asEagerSingleton();
-        bind(new TypeLiteral<CacheController<UUID, Long>>() {}).annotatedWith(ACCOUNT_RECORD_ID_CACHE_NAMED).toProvider(new CacheControllerProvider<UUID, Long>(Cachable.ACCOUNT_RECORD_ID_CACHE_NAME)).asEagerSingleton();
+        final CacheConfig config = new ConfigurationObjectFactory(configSource).build(CacheConfig.class);
+        bind(CacheConfig.class).toInstance(config);
 
-        bind(CacheLoader.class).annotatedWith(TENANT_RECORD_ID_CACHE_NAMED).to(TenantRecordIdCacheLoader.class).asEagerSingleton();
-        bind(new TypeLiteral<CacheController<UUID, Long>>() {}).annotatedWith(TENANT_RECORD_ID_CACHE_NAMED).toProvider(new CacheControllerProvider<UUID, Long>(Cachable.TENANT_RECORD_ID_CACHE_NAME)).asEagerSingleton();
+        // EhCache specifics
+        bind(CacheManager.class).toProvider(EhCacheCacheManagerProvider.class).asEagerSingleton();
 
-        bind(CacheControllerDispatcher.class).asEagerSingleton();
+        // Kill Bill generic cache dispatcher
+        bind(CacheControllerDispatcher.class).toProvider(CacheControllerDispatcherProvider.class).asEagerSingleton();
     }
 }
diff --git a/util/src/main/resources/ehcache.xml b/util/src/main/resources/ehcache.xml
index 684c3c8..07e2a73 100644
--- a/util/src/main/resources/ehcache.xml
+++ b/util/src/main/resources/ehcache.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  ~ Copyright 2010-2012 Ning, Inc.
+  ~ Copyright 2010-2013 Ning, Inc.
   ~
   ~ Ning 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
@@ -17,17 +17,17 @@
   -->
 
 <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="ehcache.xsd" >
+         xsi:noNamespaceSchemaLocation="ehcache.xsd">
 
-  <defaultCache
-      maxElementsInMemory="0"
-      maxElementsOnDisk="0"
-      eternal="false"
-      timeToIdleSeconds="0"
-      timeToLiveSeconds="0"
-      overflowToDisk="false"
-      statistics="true"
-      />
+    <defaultCache
+            maxElementsInMemory="0"
+            maxElementsOnDisk="0"
+            eternal="false"
+            timeToIdleSeconds="0"
+            timeToLiveSeconds="0"
+            overflowToDisk="false"
+            statistics="true"
+            />
 
     <cache name="record-id"
            maxElementsInMemory="100000"
@@ -35,10 +35,12 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
     </cache>
 
     <cache name="tenant-record-id"
@@ -47,10 +49,12 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
     </cache>
 
     <cache name="account-record-id"
@@ -59,10 +63,42 @@
            eternal="true"
            overflowToDisk="false"
            diskPersistent="false"
-           memoryStoreEvictionPolicy="LFU">
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
         <cacheEventListenerFactory
                 class="com.ning.billing.util.cache.ExpirationListenerFactory"
-                properties="" />
+                properties=""/>
+    </cache>
+
+    <cache name="audit-log"
+           maxElementsInMemory="500000"
+           maxElementsOnDisk="0"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="600"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="com.ning.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
+    </cache>
+
+    <cache name="audit-log-via-history"
+           maxElementsInMemory="500000"
+           maxElementsOnDisk="0"
+           timeToIdleSeconds="600"
+           timeToLiveSeconds="600"
+           overflowToDisk="false"
+           diskPersistent="false"
+           memoryStoreEvictionPolicy="LFU"
+           statistics="true"
+            >
+        <cacheEventListenerFactory
+                class="com.ning.billing.util.cache.ExpirationListenerFactory"
+                properties=""/>
     </cache>
 </ehcache>
 
diff --git a/util/src/test/java/com/ning/billing/GuicyKillbillTestWithEmbeddedDBModule.java b/util/src/test/java/com/ning/billing/GuicyKillbillTestWithEmbeddedDBModule.java
index 7f44985..c5567a1 100644
--- a/util/src/test/java/com/ning/billing/GuicyKillbillTestWithEmbeddedDBModule.java
+++ b/util/src/test/java/com/ning/billing/GuicyKillbillTestWithEmbeddedDBModule.java
@@ -27,9 +27,9 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 
 public class GuicyKillbillTestWithEmbeddedDBModule extends GuicyKillbillTestModule {
 
-    private final static Logger log = LoggerFactory.getLogger(GuicyKillbillTestWithEmbeddedDBModule.class);
+    private static final Logger log = LoggerFactory.getLogger(GuicyKillbillTestWithEmbeddedDBModule.class);
 
-    private static DBTestingHelper instance = getDBTestingHelper();
+    private static final DBTestingHelper instance = getDBTestingHelper();
 
     public static synchronized DBTestingHelper getDBTestingHelper() {
         if (instance == null) {
diff --git a/util/src/test/java/com/ning/billing/util/audit/dao/TestDefaultAuditDao.java b/util/src/test/java/com/ning/billing/util/audit/dao/TestDefaultAuditDao.java
index 8ed8aa5..6fbc2a9 100644
--- a/util/src/test/java/com/ning/billing/util/audit/dao/TestDefaultAuditDao.java
+++ b/util/src/test/java/com/ning/billing/util/audit/dao/TestDefaultAuditDao.java
@@ -38,7 +38,7 @@ import com.ning.billing.util.tag.dao.TagModelDao;
 
 public class TestDefaultAuditDao extends UtilTestSuiteWithEmbeddedDB {
 
-    private UUID tagId;
+    private TagModelDao tag;
 
     @Test(groups = "slow")
     public void testRetrieveAuditsDirectly() throws Exception {
@@ -60,11 +60,25 @@ public class TestDefaultAuditDao extends UtilTestSuiteWithEmbeddedDB {
         addTag();
 
         for (final AuditLevel level : AuditLevel.values()) {
-            final List<AuditLog> auditLogs = auditDao.getAuditLogsForId(TableName.TAG, tagId, level, internalCallContext);
+            final List<AuditLog> auditLogs = auditDao.getAuditLogsForId(TableName.TAG, tag.getId(), level, internalCallContext);
             verifyAuditLogsForTag(auditLogs, level);
         }
     }
 
+    @Test(groups = "slow")
+    public void testVerifyAuditCachesAreCleared() throws Exception {
+        addTag();
+        final List<AuditLog> firstAuditLogs = auditDao.getAuditLogsForId(TableName.TAG, tag.getId(), AuditLevel.FULL, internalCallContext);
+        Assert.assertEquals(firstAuditLogs.size(), 1);
+        Assert.assertEquals(firstAuditLogs.get(0).getChangeType(), ChangeType.INSERT);
+
+        tagDao.deleteTag(tag.getObjectId(), tag.getObjectType(), tag.getTagDefinitionId(), internalCallContext);
+        final List<AuditLog> secondAuditLogs = auditDao.getAuditLogsForId(TableName.TAG, tag.getId(), AuditLevel.FULL, internalCallContext);
+        Assert.assertEquals(secondAuditLogs.size(), 2);
+        Assert.assertEquals(secondAuditLogs.get(0).getChangeType(), ChangeType.INSERT);
+        Assert.assertEquals(secondAuditLogs.get(1).getChangeType(), ChangeType.DELETE);
+    }
+
     private void addTag() throws TagDefinitionApiException, TagApiException {
         // Create a tag definition
         final TagDefinitionModelDao tagDefinition = tagDefinitionDao.create(UUID.randomUUID().toString().substring(0, 5),
@@ -75,14 +89,13 @@ public class TestDefaultAuditDao extends UtilTestSuiteWithEmbeddedDB {
         // Create a tag
         final UUID objectId = UUID.randomUUID();
 
-        final Tag tag = new DescriptiveTag(tagDefinition.getId(), ObjectType.ACCOUNT, objectId, clock.getUTCNow());
+        final Tag theTag = new DescriptiveTag(tagDefinition.getId(), ObjectType.ACCOUNT, objectId, clock.getUTCNow());
 
-        tagDao.create(new TagModelDao(tag), internalCallContext);
+        tagDao.create(new TagModelDao(theTag), internalCallContext);
         final List<TagModelDao> tags = tagDao.getTagsForObject(objectId, ObjectType.ACCOUNT, internalCallContext);
         Assert.assertEquals(tags.size(), 1);
-        final TagModelDao savedTag = tags.get(0);
-        Assert.assertEquals(savedTag.getTagDefinitionId(), tagDefinition.getId());
-        tagId = savedTag.getId();
+        tag = tags.get(0);
+        Assert.assertEquals(tag.getTagDefinitionId(), tagDefinition.getId());
     }
 
     private void verifyAuditLogsForTag(final List<AuditLog> auditLogs, final AuditLevel level) {
diff --git a/util/src/test/java/com/ning/billing/util/cache/TestCache.java b/util/src/test/java/com/ning/billing/util/cache/TestCache.java
index 1c4c113..07680f1 100644
--- a/util/src/test/java/com/ning/billing/util/cache/TestCache.java
+++ b/util/src/test/java/com/ning/billing/util/cache/TestCache.java
@@ -18,24 +18,16 @@ package com.ning.billing.util.cache;
 
 import java.util.UUID;
 
-import javax.inject.Inject;
-
 import org.testng.Assert;
-import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
 import com.ning.billing.ObjectType;
-import com.ning.billing.mock.glue.MockDbHelperModule;
 import com.ning.billing.util.UtilTestSuiteWithEmbeddedDB;
 import com.ning.billing.util.cache.Cachable.CacheType;
-import com.ning.billing.util.dao.NonEntityDao;
 import com.ning.billing.util.entity.dao.EntitySqlDao;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionWrapper;
 import com.ning.billing.util.entity.dao.EntitySqlDaoTransactionalJdbiWrapper;
 import com.ning.billing.util.entity.dao.EntitySqlDaoWrapperFactory;
-import com.ning.billing.util.glue.CacheModule;
-import com.ning.billing.util.glue.ClockModule;
-import com.ning.billing.util.glue.NonEntityDaoModule;
 import com.ning.billing.util.tag.dao.TagModelDao;
 import com.ning.billing.util.tag.dao.TagSqlDao;
 
@@ -72,7 +64,8 @@ public class TestCache extends UtilTestSuiteWithEmbeddedDB {
         final CacheController<Object, Object> cache = controlCacheDispatcher.getCacheController(CacheType.RECORD_ID);
         Object result = null;
         if (cache != null) {
-            result =  cache.get(tagId.toString(), ObjectType.TAG);
+            // Keys are upper cased by convention
+            result = cache.get(tagId.toString().toUpperCase(), new CacheLoaderArgument(ObjectType.TAG));
         }
         return (Long) result;
     }
diff --git a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
index 71a03f4..1babdf5 100644
--- a/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
+++ b/util/src/test/java/com/ning/billing/util/notificationq/TestNotificationQueue.java
@@ -87,7 +87,7 @@ public class TestNotificationQueue extends UtilTestSuiteWithEmbeddedDB {
     @BeforeClass(groups = "slow")
     public void beforeClass() throws Exception {
         super.beforeClass();
-        entitySqlDaoTransactionalJdbiWrapper = new EntitySqlDaoTransactionalJdbiWrapper(getDBI(), clock, cacheControllerDispatcher, nonEntityDao);
+        entitySqlDaoTransactionalJdbiWrapper = new EntitySqlDaoTransactionalJdbiWrapper(getDBI(), clock, controlCacheDispatcher, nonEntityDao);
     }
 
     @Override
diff --git a/util/src/test/java/com/ning/billing/util/UtilTestSuiteWithEmbeddedDB.java b/util/src/test/java/com/ning/billing/util/UtilTestSuiteWithEmbeddedDB.java
index 410b21a..39a9731 100644
--- a/util/src/test/java/com/ning/billing/util/UtilTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/com/ning/billing/util/UtilTestSuiteWithEmbeddedDB.java
@@ -52,8 +52,6 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
     @Inject
     protected InternalCallContextFactory internalCallContextFactory;
     @Inject
-    protected CacheControllerDispatcher cacheControllerDispatcher;
-    @Inject
     protected DefaultCustomFieldUserApi customFieldUserApi;
     @Inject
     protected CustomFieldDao customFieldDao;
@@ -76,8 +74,9 @@ public abstract class UtilTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
 
     @Override
     @BeforeMethod(groups = "slow")
-    public void beforeMethod() throws Exception  {
+    public void beforeMethod() throws Exception {
         super.beforeMethod();
+        controlCacheDispatcher.clearAll();
         eventBus.start();
     }