From 0cac723eac3a708eb99deb66bb20b77547211eb1 Mon Sep 17 00:00:00 2001 From: Roland Praml Date: Mon, 20 May 2019 10:25:18 +0200 Subject: [PATCH] Query Cache will not always be invalidated (#1717) * provide failing test, that shows missing cache invalidation * FIX: proper cache invalidation when dependent table will be modified --- .../deploy/BeanDescriptorCacheHelp.java | 1 + .../server/transaction/BeanPersistIds.java | 1 + .../cache/TestQueryCacheTableDependency.java | 70 +++++++++++++++++++ .../tests/model/basic/cache/ECacheChild.java | 52 ++++++++++++++ .../tests/model/basic/cache/ECacheRoot.java | 38 ++++++++++ 5 files changed, 162 insertions(+) create mode 100644 src/test/java/org/tests/model/basic/cache/ECacheChild.java create mode 100644 src/test/java/org/tests/model/basic/cache/ECacheRoot.java diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java index 398b4a31b..a0622868b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -220,6 +220,7 @@ final class BeanDescriptorCacheHelp { */ private void queryCacheClear(CacheChangeSet changeSet) { if (queryCache != null) { + changeSet.addInvalidate(desc); changeSet.addClearQuery(desc); } } diff --git a/src/main/java/io/ebeaninternal/server/transaction/BeanPersistIds.java b/src/main/java/io/ebeaninternal/server/transaction/BeanPersistIds.java index bb1690bfe..d7d24eefc 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/BeanPersistIds.java +++ b/src/main/java/io/ebeaninternal/server/transaction/BeanPersistIds.java @@ -128,6 +128,7 @@ public class BeanPersistIds implements BinaryWritable { * Notify the cache of this event that came from another server in the cluster. */ public void notifyCache(CacheChangeSet changeSet) { + changeSet.addInvalidate(beanDescriptor); changeSet.addClearQuery(beanDescriptor); if (ids != null) { changeSet.addBeanRemoveMany(beanDescriptor, ids); diff --git a/src/test/java/org/tests/cache/TestQueryCacheTableDependency.java b/src/test/java/org/tests/cache/TestQueryCacheTableDependency.java index ee12f6794..8ee240bd0 100644 --- a/src/test/java/org/tests/cache/TestQueryCacheTableDependency.java +++ b/src/test/java/org/tests/cache/TestQueryCacheTableDependency.java @@ -1,13 +1,18 @@ package org.tests.cache; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; +import io.ebean.Query; import io.ebean.cache.ServerCache; + import org.junit.Test; import org.tests.model.basic.Address; import org.tests.model.basic.Contact; import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; +import org.tests.model.basic.cache.ECacheChild; +import org.tests.model.basic.cache.ECacheRoot; import java.util.List; @@ -122,4 +127,69 @@ public class TestQueryCacheTableDependency extends BaseTestCase { assertThat(custCount1).isEqualTo(0); } + + @Test + public void testCache_withInvalidateQueryCache() throws InterruptedException { + // So Address has no query cache enabled, but has @InvalidateQueryCache annotation + Address root = new Address(); + root.setCity("new york"); + DB.save(root); + + Customer child = new Customer(); + child.setName("bobby"); + child.setBillingAddress(root); + DB.save(child); + DB.getServerCacheManager().getQueryCache(Address.class).clear(); + DB.getServerCacheManager().getQueryCache(Customer.class).clear(); + // Test preparation finished, start the test + Thread.sleep(10); + + Query query = DB.find(Customer.class).where().eq("billingAddress.city", "new york").query(); + query.setUseQueryCache(true); + + assertThat(query.findCount()).isEqualTo(1); + + root.setCity("san francisco"); + DB.save(root); + + assertThat(query.findCount()).isEqualTo(0); + + // clean up + DB.delete(child); + // DB.delete(root); deleted by M2O cascade. + } + + @Test + public void testCache_withEnabeldQueryCache() throws InterruptedException { + // ECacheRoot and ECacheChild have enabled query cache. + ECacheRoot root = new ECacheRoot(); + root.setName("testRoot"); + DB.save(root); + + ECacheChild child = new ECacheChild(); + child.setName("testChild"); + child.setRoot(root); + DB.save(child); + DB.getServerCacheManager().getQueryCache(ECacheChild.class).clear(); + DB.getServerCacheManager().getQueryCache(ECacheRoot.class).clear(); + // Test preparation finished, start the test + Thread.sleep(10); + // we need this thread.sleep here, because on a fast machine, DB.save(child) and computing the + // queryCacheEntry will happen in the same millisecond (or 10 milliseconds, which is the resolution + // of System.currentTimeMillis() on windows 7 & java 8) + Query query = DB.find(ECacheChild.class).where().eq("root.name", "testRoot").query(); + query.setUseQueryCache(true); + + assertThat(query.findCount()).isEqualTo(1); + + root = DB.find(ECacheRoot.class).findList().get(0); + root.setName("test2"); + DB.save(root); + + assertThat(query.findCount()).isEqualTo(0); + + // clean up + DB.delete(child); + DB.delete(root); + } } diff --git a/src/test/java/org/tests/model/basic/cache/ECacheChild.java b/src/test/java/org/tests/model/basic/cache/ECacheChild.java new file mode 100644 index 000000000..d14ae5e4d --- /dev/null +++ b/src/test/java/org/tests/model/basic/cache/ECacheChild.java @@ -0,0 +1,52 @@ +package org.tests.model.basic.cache; + +import java.util.UUID; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import io.ebean.annotation.Cache; + +@Entity +@Cache(enableQueryCache = true) +public class ECacheChild { + + @Id + @GeneratedValue + protected UUID id; + + @Size(max = 100) + private String name; + + @NotNull + @ManyToOne + private ECacheRoot root; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ECacheRoot getRoot() { + return root; + } + + public void setRoot(ECacheRoot root) { + this.root = root; + } +} diff --git a/src/test/java/org/tests/model/basic/cache/ECacheRoot.java b/src/test/java/org/tests/model/basic/cache/ECacheRoot.java new file mode 100644 index 000000000..10b9ef9df --- /dev/null +++ b/src/test/java/org/tests/model/basic/cache/ECacheRoot.java @@ -0,0 +1,38 @@ +package org.tests.model.basic.cache; + +import java.util.UUID; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.validation.constraints.Size; + +import io.ebean.annotation.Cache; + +@Entity +@Cache(enableQueryCache = true) +public class ECacheRoot { + + @Id + @GeneratedValue + protected UUID id; + + @Size(max = 100) + private String name; + + public UUID getId() { + return id; + } + + public void setId(UUID id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +}