mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 10:38:57 +00:00
#652 - ENH: L2 cache - add @CacheQueryTuning annotation
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify cache tuning for query caching on a specific entity type.
|
||||
* <p>
|
||||
* If this is not specified then the system default settings are used.
|
||||
* </p>
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface CacheQueryTuning {
|
||||
|
||||
/**
|
||||
* The maximum size for the cache.
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
int maxSize() default 0;
|
||||
|
||||
/**
|
||||
* The maximum time (in seconds) that a cache entry is allowed to stay in the
|
||||
* cache when it has not been accessed.
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
int maxIdleSecs() default 0;
|
||||
|
||||
/**
|
||||
* The maximum time (in seconds) a cache entry is allowed to stay in the
|
||||
* cache.
|
||||
* <p>
|
||||
* This is not generally required as the cache entries are automatically
|
||||
* evicted when related data changes are committed.
|
||||
* </p>
|
||||
* <p>
|
||||
* This defaults to 0 which means unlimited.
|
||||
* </p>
|
||||
*/
|
||||
int maxSecsToLive() default 0;
|
||||
|
||||
/**
|
||||
* The frequency (in seconds) that cache trimming should occur.
|
||||
* <p>
|
||||
* This is a hint for cache implementations that use background cache trimming.
|
||||
* </p>
|
||||
*/
|
||||
int trimFrequency() default 0;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean.cache;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
|
||||
/**
|
||||
@@ -30,13 +31,13 @@ public class ServerCacheOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create merging default options with the deployment specified ones.
|
||||
* Create from the cacheTuning deployment annotation.
|
||||
*/
|
||||
public ServerCacheOptions(ServerCacheOptions defaults) {
|
||||
this.maxSize = defaults.getMaxSize();
|
||||
this.maxIdleSecs = defaults.getMaxIdleSecs();
|
||||
this.maxSecsToLive = defaults.getMaxIdleSecs();
|
||||
this.trimFrequency = defaults.getTrimFrequency();
|
||||
public ServerCacheOptions(CacheQueryTuning cacheTuning) {
|
||||
this.maxSize = cacheTuning.maxSize();
|
||||
this.maxIdleSecs = cacheTuning.maxIdleSecs();
|
||||
this.maxSecsToLive = cacheTuning.maxSecsToLive();
|
||||
this.trimFrequency = cacheTuning.trimFrequency();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-29
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
import com.avaje.ebean.cache.ServerCache;
|
||||
import com.avaje.ebean.cache.ServerCacheFactory;
|
||||
@@ -24,29 +25,15 @@ public class DefaultCacheHolder {
|
||||
|
||||
private final ServerCacheOptions defaultOptions;
|
||||
|
||||
private final boolean useBeanTuning;
|
||||
|
||||
/**
|
||||
* Create with a cache factory and default cache options.
|
||||
*
|
||||
* @param cacheFactory the factory for creating the cache
|
||||
* @param defaultOptions the default options for tuning the cache
|
||||
* @param useBeanTuning if true then use the bean class specific tuning. This is
|
||||
* generally false for the query cache.
|
||||
*/
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory,
|
||||
ServerCacheOptions defaultOptions, boolean useBeanTuning) {
|
||||
|
||||
public DefaultCacheHolder(ServerCacheFactory cacheFactory, ServerCacheOptions defaultOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.defaultOptions = defaultOptions;
|
||||
this.useBeanTuning = useBeanTuning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default cache options.
|
||||
*/
|
||||
public ServerCacheOptions getDefaultOptions() {
|
||||
return defaultOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +48,7 @@ public class DefaultCacheHolder {
|
||||
synchronized (monitor) {
|
||||
cache = synchMap.get(cacheKey);
|
||||
if (cache == null) {
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey);
|
||||
ServerCacheOptions options = getCacheOptions(cacheKey, type);
|
||||
cache = cacheFactory.createCache(type, cacheKey, options);
|
||||
synchMap.put(cacheKey, cache);
|
||||
concMap.put(cacheKey, cache);
|
||||
@@ -94,25 +81,41 @@ public class DefaultCacheHolder {
|
||||
/**
|
||||
* Return the cache options for a given bean type.
|
||||
*/
|
||||
private ServerCacheOptions getCacheOptions(String beanType) {
|
||||
ServerCacheOptions getCacheOptions(String beanType, ServerCacheType type) {
|
||||
|
||||
if (useBeanTuning) {
|
||||
// read the deployment annotation
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
try {
|
||||
Class<?> cls = Class.forName(beanType);
|
||||
switch (type) {
|
||||
case QUERY:
|
||||
return getQueryOptions(cls);
|
||||
default:
|
||||
return getBeanOptions(cls);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
private ServerCacheOptions getQueryOptions(Class<?> cls) {
|
||||
CacheQueryTuning tuning = cls.getAnnotation(CacheQueryTuning.class);
|
||||
if (tuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(tuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
private ServerCacheOptions getBeanOptions(Class<?> cls) {
|
||||
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
|
||||
if (cacheTuning != null) {
|
||||
ServerCacheOptions o = new ServerCacheOptions(cacheTuning);
|
||||
o.applyDefaults(defaultOptions);
|
||||
return o;
|
||||
}
|
||||
return defaultOptions.copy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+17
-2
@@ -34,7 +34,6 @@ public class DefaultServerCache implements ServerCache {
|
||||
*/
|
||||
public static final CompareByLastAccess BY_LAST_ACCESS = new CompareByLastAccess();
|
||||
|
||||
|
||||
/**
|
||||
* The underlying map (ConcurrentHashMap or similar)
|
||||
*/
|
||||
@@ -89,7 +88,23 @@ public class DefaultServerCache implements ServerCache {
|
||||
this.maxSize = maxSize;
|
||||
this.maxIdleSecs = maxIdleSecs;
|
||||
this.maxSecsToLive = maxSecsToLive;
|
||||
this.trimFrequency = trimFrequency;
|
||||
this.trimFrequency = determineTrim(maxIdleSecs, maxSecsToLive, trimFrequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine a good trimFrequency as half of maxIdleSecs (or maxSecsToLive).
|
||||
*/
|
||||
int determineTrim(int maxIdleSecs, int maxSecsToLive, int trimFrequency) {
|
||||
if (trimFrequency > 0) {
|
||||
return trimFrequency;
|
||||
}
|
||||
if (maxIdleSecs > 0) {
|
||||
return maxIdleSecs / 2 - 1;
|
||||
}
|
||||
if (maxSecsToLive > 0) {
|
||||
return maxSecsToLive / 2 - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-4
@@ -31,10 +31,10 @@ public class DefaultServerCacheManager implements ServerCacheManager {
|
||||
*/
|
||||
public DefaultServerCacheManager(ServerCacheFactory cacheFactory, ServerCacheOptions defaultBeanOptions, ServerCacheOptions defaultQueryOptions) {
|
||||
this.cacheFactory = cacheFactory;
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions, true);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions, false);
|
||||
this.beanCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.queryCache = new DefaultCacheHolder(cacheFactory, defaultQueryOptions);
|
||||
this.naturalKeyCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
this.collectionIdsCache = new DefaultCacheHolder(cacheFactory, defaultBeanOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheType;
|
||||
import com.avaje.tests.model.basic.Article;
|
||||
import com.avaje.tests.model.basic.Order;
|
||||
import com.avaje.tests.model.basic.Product;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DefaultCacheHolder_getCacheOptions_Test {
|
||||
|
||||
private DefaultCacheHolder cacheHolder;
|
||||
|
||||
public DefaultCacheHolder_getCacheOptions_Test() {
|
||||
|
||||
ServerCacheOptions defaultOptions = new ServerCacheOptions();
|
||||
defaultOptions.setMaxSize(10000);
|
||||
defaultOptions.setMaxSecsToLive(120);
|
||||
|
||||
this.cacheHolder = new DefaultCacheHolder(null, defaultOptions);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanOptions_when_set() {
|
||||
|
||||
ServerCacheOptions options = cacheHolder.getCacheOptions(Article.class.getName(), ServerCacheType.BEAN);
|
||||
assertEquals(options.getMaxSecsToLive(), 45);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanOptions_when_notSet_expect_default() {
|
||||
|
||||
ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class.getName(), ServerCacheType.BEAN);
|
||||
assertEquals(options.getMaxSecsToLive(), 120);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryOptions_when_set() {
|
||||
|
||||
ServerCacheOptions options = cacheHolder.getCacheOptions(Product.class.getName(), ServerCacheType.QUERY);
|
||||
assertEquals(options.getMaxSecsToLive(), 15);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryOptions_when_notSet_expect_default() {
|
||||
|
||||
ServerCacheOptions options = cacheHolder.getCacheOptions(Order.class.getName(), ServerCacheType.QUERY);
|
||||
assertEquals(options.getMaxSecsToLive(), 120);
|
||||
}
|
||||
}
|
||||
+32
-3
@@ -51,9 +51,38 @@ public class DefaultServerCacheTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTrimSize() throws Exception {
|
||||
public void trimFreq_halfIdle() throws Exception {
|
||||
|
||||
DefaultServerCache cache = createCache();
|
||||
assertEquals(90, cache.getTrimSize());
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, 10000, 10, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfIdle_withRounding() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, 10000, 11, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, 10000, 0, 20, 0);
|
||||
assertEquals(cache.trimFrequency, 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_halfTTL_withRounding() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, 10000, 0, 21, 0);
|
||||
assertEquals(cache.trimFrequency, 9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void trimFreq_explicit() throws Exception {
|
||||
|
||||
DefaultServerCache cache = new DefaultServerCache("", null, 10000, 10, 20, 42);
|
||||
assertEquals(cache.trimFrequency, 42);
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.cache.ServerCacheOptions;
|
||||
import com.avaje.ebean.cache.ServerCacheStatistics;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
|
||||
public class DefaultServerCache_RunEvictionTest {
|
||||
|
||||
|
||||
private DefaultServerCache createCache() {
|
||||
|
||||
ServerCacheOptions cacheOptions = new ServerCacheOptions();
|
||||
cacheOptions.setMaxSize(10000);
|
||||
cacheOptions.setMaxIdleSecs(1);
|
||||
cacheOptions.setMaxSecsToLive(2);
|
||||
cacheOptions.setTrimFrequency(1);
|
||||
|
||||
return new DefaultServerCache("foo", cacheOptions);
|
||||
}
|
||||
|
||||
private final DefaultServerCache cache;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
public DefaultServerCache_RunEvictionTest(){
|
||||
this.cache = createCache();
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void runEvict() throws InterruptedException {
|
||||
|
||||
for (int i = 0; i < 15; i++) {
|
||||
doStuff();
|
||||
cache.runEviction();
|
||||
ServerCacheStatistics statistics = cache.getStatistics(true);
|
||||
System.out.println(statistics);
|
||||
Thread.sleep(500);
|
||||
}
|
||||
}
|
||||
|
||||
private void doStuff() {
|
||||
|
||||
for (int i = 0; i < 500; i++) {
|
||||
String key = ""+random.nextInt(20000);
|
||||
|
||||
int mode = random.nextInt(10);
|
||||
if (mode < 8) {
|
||||
cache.get(key);
|
||||
} else {
|
||||
cache.put(key, key+"-"+System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +1,64 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.annotation.CacheTuning;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@CacheStrategy
|
||||
@CacheTuning(maxSecsToLive = 45)
|
||||
@Entity
|
||||
public class Article extends BasicDomain {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
String name;
|
||||
|
||||
String name;
|
||||
|
||||
String author;
|
||||
|
||||
@OneToMany(cascade=CascadeType.ALL)
|
||||
List<Section> sections;
|
||||
String author;
|
||||
|
||||
public Article() {
|
||||
|
||||
}
|
||||
|
||||
public Article(String name, String author) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@OneToMany(cascade = CascadeType.ALL)
|
||||
List<Section> sections;
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public Article() {
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
public Article(String name, String author) {
|
||||
this.name = name;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public List<Section> getSections() {
|
||||
return sections;
|
||||
}
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setSections(List<Section> sections) {
|
||||
this.sections = sections;
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public List<Section> getSections() {
|
||||
return sections;
|
||||
}
|
||||
|
||||
public void setSections(List<Section> sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
|
||||
public void addSection(Section s) {
|
||||
if (sections == null) {
|
||||
sections = new ArrayList<Section>();
|
||||
}
|
||||
|
||||
public void addSection(Section s){
|
||||
if (sections == null){
|
||||
sections = new ArrayList<Section>();
|
||||
}
|
||||
sections.add(s);
|
||||
}
|
||||
|
||||
sections.add(s);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import javax.persistence.Table;
|
||||
import javax.persistence.Version;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import com.avaje.ebean.annotation.CacheQueryTuning;
|
||||
import com.avaje.ebean.annotation.CacheStrategy;
|
||||
import com.avaje.ebean.annotation.CreatedTimestamp;
|
||||
import com.avaje.ebean.annotation.DocStore;
|
||||
@@ -18,6 +19,7 @@ import com.avaje.ebean.annotation.DocStore;
|
||||
*/
|
||||
@DocStore
|
||||
@CacheStrategy
|
||||
@CacheQueryTuning(maxSecsToLive = 15)
|
||||
@Entity
|
||||
@Table(name = "o_product")
|
||||
public class Product implements Serializable {
|
||||
|
||||
Reference in New Issue
Block a user