mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#1427 - QueryCache should be cleared, if one of a dependent bean is updated
Initial work, does not include propagation across cluster.
This commit is contained in:
@@ -3,6 +3,8 @@ package io.ebeaninternal.server.cache;
|
||||
import io.ebean.cache.ServerCacheFactory;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import io.ebeaninternal.server.transaction.TableModState;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.junit.Test;
|
||||
@@ -17,10 +19,17 @@ public class DefaultCacheHolderTest {
|
||||
private final ServerCacheFactory cacheFactory = new DefaultServerCacheFactory();
|
||||
private final ServerCacheOptions defaultOptions = new ServerCacheOptions();
|
||||
|
||||
@Test
|
||||
public void getCache_normal() throws Exception {
|
||||
private CacheManagerOptions options() {
|
||||
return new CacheManagerOptions(null, new ServerConfig(), true)
|
||||
.with(defaultOptions, defaultOptions)
|
||||
.with(cacheFactory, new TableModState());
|
||||
}
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(cacheFactory, defaultOptions, defaultOptions, null);
|
||||
|
||||
@Test
|
||||
public void getCache_normal() {
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(options());
|
||||
|
||||
DefaultServerCache cache = cache(holder, Customer.class, "customer");
|
||||
assertThat(cache.getName()).isEqualTo("customer_B");
|
||||
@@ -40,7 +49,9 @@ public class DefaultCacheHolderTest {
|
||||
@Test
|
||||
public void getCache_multiTenant() throws Exception {
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(cacheFactory, defaultOptions, defaultOptions, tenantId::get);
|
||||
CacheManagerOptions builder = options().with(tenantId::get);
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(builder);
|
||||
|
||||
tenantId.set("ten_1");
|
||||
DefaultServerCache cache = cache(holder, Customer.class, "customer");
|
||||
@@ -48,26 +59,26 @@ public class DefaultCacheHolderTest {
|
||||
|
||||
cache.put("1", "value-for-tenant1");
|
||||
cache.put("2", "an other value-for-tenant1");
|
||||
|
||||
|
||||
assertThat(cache.size()).isEqualTo(2);
|
||||
|
||||
|
||||
tenantId.set("ten_2");
|
||||
|
||||
cache.put("1", "value-for-tenant2");
|
||||
cache.put("2", "an other value-for-tenant2");
|
||||
|
||||
|
||||
assertThat(cache.size()).isEqualTo(4);
|
||||
|
||||
|
||||
assertThat(cache.get("1")).isEqualTo("value-for-tenant2");
|
||||
assertThat(cache.get("2")).isEqualTo("an other value-for-tenant2");
|
||||
|
||||
|
||||
|
||||
|
||||
tenantId.set("ten_1");
|
||||
|
||||
|
||||
assertThat(cache.get("1")).isEqualTo("value-for-tenant1");
|
||||
assertThat(cache.get("2")).isEqualTo("an other value-for-tenant1");
|
||||
|
||||
Exception exInThread[] = new Exception[1];
|
||||
|
||||
Exception exInThread[] = new Exception[1];
|
||||
Thread t = new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -77,29 +88,29 @@ public class DefaultCacheHolderTest {
|
||||
|
||||
cache.put("1", "value-for-tenant2");
|
||||
cache.put("2", "an other value-for-tenant2");
|
||||
|
||||
|
||||
tenantId.set(null);
|
||||
|
||||
|
||||
cache.clear();
|
||||
} catch (Exception e) {
|
||||
exInThread[0] = e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// do some async work
|
||||
t.start();
|
||||
t.join();
|
||||
if (exInThread[0] != null) {
|
||||
if (exInThread[0] != null) {
|
||||
throw exInThread[0];
|
||||
}
|
||||
assertThat(cache.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void clearAll() throws Exception {
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(cacheFactory, defaultOptions, defaultOptions, null);
|
||||
public void clearAll() {
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(options());
|
||||
DefaultServerCache cache = cache(holder, Customer.class, "customer");
|
||||
cache.put("foo", "foo");
|
||||
assertThat(cache.size()).isEqualTo(1);
|
||||
@@ -109,8 +120,11 @@ public class DefaultCacheHolderTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAll_multiTenant() throws Exception {
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(cacheFactory, defaultOptions, defaultOptions, tenantId::get);
|
||||
public void clearAll_multiTenant() {
|
||||
|
||||
CacheManagerOptions options = options().with(tenantId::get);
|
||||
|
||||
DefaultCacheHolder holder = new DefaultCacheHolder(options);
|
||||
DefaultServerCache cache = cache(holder, Customer.class, "customer");
|
||||
cache.put("foo", "foo");
|
||||
assertThat(cache.size()).isEqualTo(1);
|
||||
@@ -118,5 +132,5 @@ public class DefaultCacheHolderTest {
|
||||
holder.clearAll();
|
||||
assertThat(cache.size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+5
-1
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import io.ebean.config.ServerConfig;
|
||||
import org.tests.model.basic.Article;
|
||||
import org.tests.model.basic.Order;
|
||||
import org.tests.model.basic.Product;
|
||||
@@ -19,7 +20,10 @@ public class DefaultCacheHolder_getCacheOptions_Test {
|
||||
defaultOptions.setMaxSize(10000);
|
||||
defaultOptions.setMaxSecsToLive(120);
|
||||
|
||||
this.cacheHolder = new DefaultCacheHolder(null, defaultOptions, defaultOptions, null);
|
||||
CacheManagerOptions builder = new CacheManagerOptions(null, new ServerConfig(), true)
|
||||
.with(defaultOptions, defaultOptions);
|
||||
|
||||
this.cacheHolder = new DefaultCacheHolder(builder);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DefaultServerCacheConfigTest {
|
||||
|
||||
|
||||
private DefaultServerCacheConfig create(int maxSize, int maxIdleSecs, int maxSecsToLive, int trimFreq) {
|
||||
ServerCacheOptions options = new ServerCacheOptions();
|
||||
options.setMaxSize(maxSize);
|
||||
options.setMaxIdleSecs(maxIdleSecs);
|
||||
options.setMaxSecsToLive(maxSecsToLive);
|
||||
options.setTrimFrequency(trimFreq);
|
||||
|
||||
return new DefaultServerCacheConfig(new ServerCacheConfig(null, null, options, null, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfIdle() {
|
||||
|
||||
assertEquals(create(10000,10,20, 0).determineTrimFrequency(), 4);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfIdle_withRounding() {
|
||||
|
||||
assertEquals(create(10000,11,20, 0).determineTrimFrequency(), 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL() {
|
||||
|
||||
assertEquals(create(10000,0,20, 0).determineTrimFrequency(), 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL_withRounding() {
|
||||
|
||||
assertEquals(create(10000,0,21, 0).determineTrimFrequency(), 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_explicit() {
|
||||
|
||||
assertEquals(create(10000,10,20, 42).determineTrimFrequency(), 42);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -15,11 +17,13 @@ public class DefaultServerCacheTest {
|
||||
cacheOptions.setMaxSecsToLive(600);
|
||||
cacheOptions.setTrimFrequency(60);
|
||||
|
||||
return new DefaultServerCache("foo", null, cacheOptions);
|
||||
ServerCacheConfig con = new ServerCacheConfig(ServerCacheType.BEAN, "foo", cacheOptions, null, null);
|
||||
DefaultServerCacheConfig config = new DefaultServerCacheConfig(con);
|
||||
return new DefaultServerCache(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHitRatio() throws Exception {
|
||||
public void testGetHitRatio() {
|
||||
|
||||
DefaultServerCache cache = createCache();
|
||||
assertEquals(0, cache.getHitRatio());
|
||||
@@ -34,7 +38,7 @@ public class DefaultServerCacheTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() throws Exception {
|
||||
public void testSize() {
|
||||
|
||||
DefaultServerCache cache = createCache();
|
||||
assertEquals(0, cache.size());
|
||||
@@ -50,39 +54,5 @@ public class DefaultServerCacheTest {
|
||||
assertEquals(0, cache.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfIdle() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, null, 10000, 10, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfIdle_withRounding() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, null, 10000, 11, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, null, 10000, 0, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL_withRounding() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, null, 10000, 0, 21, 0);
|
||||
assertEquals(cache.trimFrequency, 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_explicit() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, null, 10000, 10, 20, 42);
|
||||
assertEquals(cache.trimFrequency, 42);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -1,7 +1,9 @@
|
||||
package io.ebeaninternal.server.cache;
|
||||
|
||||
import io.ebean.cache.ServerCacheConfig;
|
||||
import io.ebean.cache.ServerCacheOptions;
|
||||
import io.ebean.cache.ServerCacheStatistics;
|
||||
import io.ebean.cache.ServerCacheType;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -19,7 +21,8 @@ public class DefaultServerCache_RunEvictionTest {
|
||||
cacheOptions.setMaxSecsToLive(2);
|
||||
cacheOptions.setTrimFrequency(1);
|
||||
|
||||
return new DefaultServerCache("foo", null, cacheOptions);
|
||||
ServerCacheConfig con = new ServerCacheConfig(ServerCacheType.BEAN, "foo", cacheOptions, null, null);
|
||||
return new DefaultServerCache(new DefaultServerCacheConfig(con));
|
||||
}
|
||||
|
||||
private final DefaultServerCache cache;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.ebeaninternal.server.transaction;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TableModStateTest {
|
||||
|
||||
private TableModState tableModState = new TableModState();
|
||||
|
||||
@Test
|
||||
public void isValid() {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
tableModState.touch(setOf("one", "two", "three"), now);
|
||||
|
||||
// empty
|
||||
assertTrue(tableModState.isValid(Collections.emptySet(), 12L));
|
||||
|
||||
// no entry
|
||||
assertTrue(tableModState.isValid(setOf("noEntry"), 12L));
|
||||
|
||||
// later timestamp
|
||||
assertTrue(tableModState.isValid(setOf("one"), now + 1));
|
||||
assertTrue(tableModState.isValid(setOf("one", "two", "noEntry"), now + 1));
|
||||
|
||||
// invalid
|
||||
assertFalse(tableModState.isValid(setOf("one"), now - 1));
|
||||
assertFalse(tableModState.isValid(setOf("one", "two"), now - 1));
|
||||
assertFalse(tableModState.isValid(setOf("three", "two"), now - 1));
|
||||
|
||||
}
|
||||
|
||||
private Set<String> setOf(String... tables) {
|
||||
Set<String> touched = new HashSet<>();
|
||||
Collections.addAll(touched, tables);
|
||||
return touched;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.tests.cache;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.cache.ServerCache;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Address;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.ResetBasicData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestQueryCacheTableDependency extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testFindCountOnDependent() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
ServerCache customerCache = Ebean.getServerCacheManager().getQueryCache(Customer.class);
|
||||
customerCache.clear();
|
||||
|
||||
List<Address> addrs = Ebean.find(Address.class)
|
||||
.where().eq("line2", "St Lukes")
|
||||
.findList();
|
||||
|
||||
int custs = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
|
||||
.where().eq("billingAddress.line2", "St Lukes")
|
||||
.findCount();
|
||||
|
||||
assertThat(custs).isEqualTo(3);
|
||||
|
||||
custs = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
|
||||
.where().eq("billingAddress.line2", "St Lukes")
|
||||
.findCount();
|
||||
|
||||
assertThat(custs).isEqualTo(3);
|
||||
|
||||
Address a1 = addrs.get(0);
|
||||
a1.setLine2("St Lucky");
|
||||
Ebean.save(a1);
|
||||
|
||||
custs = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
|
||||
.where().eq("billingAddress.line2", "St Lukes")
|
||||
.findCount();
|
||||
|
||||
assertThat(custs).isEqualTo(2); // cache says 3
|
||||
|
||||
|
||||
custs = Ebean.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
|
||||
.where().eq("billingAddress.line2", "St Lucky")
|
||||
.findCount();
|
||||
|
||||
assertThat(custs).isEqualTo(1);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.tests.model.basic;
|
||||
|
||||
import io.ebean.annotation.InvalidateQueryCache;
|
||||
import org.tests.model.basic.metaannotation.SizeMedium;
|
||||
|
||||
import javax.persistence.Column;
|
||||
@@ -14,6 +15,11 @@ import java.sql.Timestamp;
|
||||
/**
|
||||
* Address entity bean.
|
||||
*/
|
||||
// Address is not L2 cached directly but it is joined to queries that are cached
|
||||
// What InvalidateQueryCache means is that we propagate a table modification event
|
||||
// when address is changed ... and cached queries that join to address will be
|
||||
// invalidated accordingly.
|
||||
@InvalidateQueryCache
|
||||
@Entity
|
||||
@Table(name = "o_address")
|
||||
public class Address {
|
||||
|
||||
Reference in New Issue
Block a user