Caching changes and refactoring, includes Fix for Wobu - TestCacheCollectionIds test case

This commit is contained in:
Rob Bygrave
2014-04-25 03:06:22 +12:00
parent 696dabc658
commit ed0fe7fd57
17 changed files with 640 additions and 254 deletions
@@ -1,10 +1,13 @@
package com.avaje.ebeaninternal.server.cache;
import java.util.Arrays;
/**
* Data held in the bean cache for cached beans.
*/
public class CachedBeanData {
private final long whenCreated;
private final Object sharableBean;
private final boolean[] loaded;
private final Object[] data;
@@ -14,6 +17,7 @@ public class CachedBeanData {
private final Object oldNaturalKey;
public CachedBeanData(Object sharableBean, boolean[] loaded, Object[] data, Object naturalKey, Object oldNaturalKey) {
this.whenCreated = System.currentTimeMillis();
this.sharableBean = sharableBean;
this.loaded = loaded;
this.data = data;
@@ -22,6 +26,10 @@ public class CachedBeanData {
this.oldNaturalKey = oldNaturalKey;
}
public String toString() {
return Arrays.toString(data);
}
/**
* Return a copy of the property data.
*/
@@ -41,35 +49,52 @@ public class CachedBeanData {
}
return dest;
}
/**
* Return when the cached data was created.
*/
public long getWhenCreated() {
return whenCreated;
}
/**
* Return a sharable (immutable read only) bean.
*/
public Object getSharableBean() {
return sharableBean;
}
/**
* Return true if this data requires an update to the natural key cache.
*/
public boolean isNaturalKeyUpdate() {
return naturalKeyUpdate;
}
/**
* Return the new/current natural key value.
*/
public Object getNaturalKey() {
return naturalKey;
}
/**
* Return the old natural key (its entry should be removed).
*/
public Object getOldNaturalKey() {
return oldNaturalKey;
}
public boolean containsProperty(int propIndex) {
return loaded[propIndex];
}
public boolean[] getLoaded() {
return loaded;
}
/**
* Return the data for the specific property.
*/
public Object getData(int i) {
return data[i];
}
/**
* Return true if the property is contained in this data.
*/
public boolean isLoaded(int i) {
return loaded[i];
}
@@ -30,13 +30,7 @@ public class CachedBeanDataToBean {
BeanPropertyAssocMany<?>[] manys = desc.propertiesMany();
for (int i = 0; i < manys.length; i++) {
BeanPropertyAssocMany<?> prop = manys[i];
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
// already loaded property
} else {
// set a lazy loading proxy
prop.createReference(bean);
}
manys[i].createReferenceIfNull(bean);
}
ebi.setLoadedLazy();
@@ -30,7 +30,7 @@ public class CachedBeanDataUpdate {
int propertyIndex = props[i].getPropertyIndex();
if (ebi.isLoadedProperty(propertyIndex)) {
if (props[i].isNaturalKey()) {
newNaturalKey = updateBean._ebean_getField(i);
newNaturalKey = updateBean._ebean_getField(propertyIndex);
}
// set the cache safe value for the property and mark it as loaded
copyData[propertyIndex] = props[i].getCacheDataValue(updateBean);
@@ -16,6 +16,10 @@ public class CachedManyIds {
this.idList = idList;
}
public String toString() {
return idList.toString();
}
public List<Object> getIdList() {
return idList;
}
@@ -4,87 +4,126 @@ package com.avaje.ebeaninternal.server.core;
* Options for controlling cache behaviour for a given type.
*/
public class CacheOptions {
private boolean useCache;
private boolean readOnly;
private String naturalKey;
private String warmingQuery;
/**
* Construct with options.
*/
public CacheOptions() {
}
/**
* Return true if this should use a cache for lazy loading.
*/
public boolean isUseCache() {
return useCache;
}
/**
* Set whether to use the bean cache for the associated type.
*/
public void setUseCache(boolean useCache) {
this.useCache = useCache;
private boolean useCache;
private boolean readOnly;
private String naturalKey;
private String warmingQuery;
private int maxIdleSecs;
private long maxSecsToLive;
/**
* Construct with options.
*/
public CacheOptions() {
}
/**
* Return true if this should use a cache for lazy loading.
*/
public boolean isUseCache() {
return useCache;
}
/**
* Set whether to use the bean cache for the associated type.
*/
public void setUseCache(boolean useCache) {
this.useCache = useCache;
}
/**
* Return the readOnly default setting.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set read Only default setting.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Return the query used to warm the cache.
*/
public String getWarmingQuery() {
return warmingQuery;
}
/**
* Set the cache warming query.
*/
public void setWarmingQuery(String warmingQuery) {
this.warmingQuery = warmingQuery;
}
/**
* Return true if a natural key is set.
*/
public boolean isUseNaturalKeyCache() {
return naturalKey != null;
}
/**
* Return the natural key property.
*/
public String getNaturalKey() {
return naturalKey;
}
/**
* Set the natural key property.
*/
public void setNaturalKey(String naturalKey) {
if (naturalKey == null || naturalKey.length() == 0) {
naturalKey = null;
} else {
this.naturalKey = naturalKey.trim();
}
}
/**
* Return the max age of entries in seconds.
*/
public long getMaxSecsToLive() {
return maxSecsToLive;
}
/**
* Return the readOnly default setting.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Set the max age of entries in seconds.
*/
public void setMaxSecsToLive(long maxSecsToLive) {
this.maxSecsToLive = maxSecsToLive;
}
/**
* Set read Only default setting.
*/
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
/**
* Set the max idle seconds.
*/
public void setMaxIdleSecs(int maxIdleSecs) {
this.maxIdleSecs = maxIdleSecs;
}
/**
* Return the query used to warm the cache.
*/
public String getWarmingQuery() {
return warmingQuery;
}
/**
* Return the max idle seconds.
*/
public int getMaxIdleSecs() {
return maxIdleSecs;
}
/**
* Set the cache warming query.
*/
public void setWarmingQuery(String warmingQuery) {
this.warmingQuery = warmingQuery;
}
/**
* Return true if the entry exceeds the maxIdleSecs or maxSecsToLive.
*/
public boolean isTooOldInMillis(long ageMillis) {
long secs = ageMillis / 1000;
return (maxIdleSecs > 0 && secs > maxIdleSecs) || (maxSecsToLive > 0 && secs > maxSecsToLive);
}
/**
* Return true if a natural key is set.
*/
public boolean isUseNaturalKeyCache() {
return naturalKey != null;
}
/**
* Return the natural key property.
*/
public String getNaturalKey() {
return naturalKey;
}
/**
* Set the natural key property.
*/
public void setNaturalKey(String naturalKey) {
if (naturalKey == null || naturalKey.length() == 0){
naturalKey = null;
} else {
this.naturalKey = naturalKey.trim();
}
}
}
@@ -1180,23 +1180,7 @@ public final class DefaultServer implements SpiEbeanServer {
}
// Hit the L2 bean cache
Object cachedBean = beanDescriptor.cacheBeanGet(query.getId(), query.isReadOnly());
if (cachedBean != null) {
if (context == null) {
context = new DefaultPersistenceContext();
}
context.put(query.getId(), cachedBean);
DLoadContext loadContext = new DLoadContext(this, beanDescriptor, query.isReadOnly(), query);
loadContext.setPersistenceContext(context);
EntityBeanIntercept ebi = ((EntityBean) cachedBean)._ebean_getIntercept();
ebi.setPersistenceContext(context);
loadContext.register(null, ebi);
}
return (T) cachedBean;
return beanDescriptor.cacheBeanGet(query, context);
}
@SuppressWarnings("unchecked")
@@ -1245,17 +1229,9 @@ public final class DefaultServer implements SpiEbeanServer {
BeanDescriptor<T> desc = beanDescriptorManager.getBeanDescriptor(q.getBeanType());
if (desc.calculateUseNaturalKeyCache(q.isUseBeanCache())) {
// check if it is a find by unique id
NaturalKeyBindParam keyBindParam = q.getNaturalKeyBindParam();
if (keyBindParam != null && desc.cacheIsNaturalKey(keyBindParam.getName())) {
Object id2 = desc.cacheNaturalKeyLookup(keyBindParam.getValue());
if (id2 != null) {
SpiQuery<T> copy = q.copy();
copy.convertWhereNaturalKeyToId(id2);
return findId(copy, t);
}
}
T bean = desc.cacheNaturalKeyLookup(q, (SpiTransaction)t);
if (bean != null) {
return bean;
}
// a query that is expected to return either 0 or 1 rows
@@ -1263,9 +1239,10 @@ public final class DefaultServer implements SpiEbeanServer {
if (list.size() == 0) {
return null;
} else if (list.size() > 1) {
String m = "Unique expecting 0 or 1 rows but got [" + list.size() + "]";
throw new PersistenceException(m);
throw new PersistenceException("Unique expecting 0 or 1 rows but got [" + list.size() + "]");
} else {
return list.get(0);
}
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.core;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -21,6 +22,7 @@ import com.avaje.ebeaninternal.api.TransactionEvent;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanManager;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.persist.PersistExecute;
import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest;
@@ -60,7 +62,7 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
*/
protected final Object parentBean;
protected final boolean isDirty;
protected final boolean dirty;
protected ConcurrencyMode concurrencyMode;
@@ -82,6 +84,17 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
private final Set<String> dirtyPropertyNames;
/**
* Flag used to detect when only many properties where updated via a cascade. Used to ensure
* appropriate cache updates occur in that case.
*/
private boolean updatedManysOnly;
/**
* Many properties that were cascade saved (and hence might need cache update later).
*/
private List<BeanPropertyAssocMany<?>> updatedManys;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr,
SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type) {
@@ -110,7 +123,7 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
this.controller = beanDescriptor.getPersistController();
this.concurrencyMode = beanDescriptor.getConcurrencyMode(intercept);
// this is ok to not use isNewOrDirty() as used for updates only
this.isDirty = intercept.isDirty();
this.dirty = intercept.isDirty();
}
/**
@@ -288,7 +301,7 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
* for EntityBeans that have not been modified.
*/
public boolean isDirty() {
return isDirty;
return dirty;
}
/**
@@ -574,5 +587,48 @@ public class PersistRequestBean<T> extends PersistRequest implements BeanPersist
public boolean isReference() {
return beanDescriptor.isReference(intercept);
}
/**
* This many property has been cascade saved. Keep note of this and update the 'many property'
* cache on post commit.
*/
public void addUpdatedManyProperty(BeanPropertyAssocMany<?> updatedAssocMany) {
//if (notifyCache) {
if (updatedManys == null) {
updatedManys = new ArrayList<BeanPropertyAssocMany<?>>(5);
}
updatedManys.add(updatedAssocMany);
//}
}
/**
* Return the list of cascade updated many properties (can be null).
*/
public List<BeanPropertyAssocMany<?>> getUpdatedManyCollections() {
return updatedManys;
}
/**
* A reference bean was saved. Check if any of its many properties where
* cascade saved and hence we need to update related many property caches.
*/
public void checkUpdatedManysOnly() {
if (!dirty && updatedManys != null) {
// set the flag and register for post commit processing if there
// is caching or registered listeners
if (idValue == null) {
this.idValue = beanDescriptor.getId(entityBean);
}
updatedManysOnly = true;
addEvent();
}
}
/**
* Return true if only many properties where updated.
*/
public boolean isUpdatedManysOnly() {
return updatedManysOnly;
}
}
@@ -0,0 +1,42 @@
package com.avaje.ebeaninternal.server.deploy;
import java.util.Collection;
import java.util.Map;
import javax.persistence.PersistenceException;
import com.avaje.ebean.bean.BeanCollection;
/**
* Utility methods for BeanCollections.
*/
public class BeanCollectionUtil {
/**
* Return the details of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
public static Collection<?> getActualEntries(Object o) {
if (o == null) {
return null;
}
if (o instanceof BeanCollection<?>) {
BeanCollection<?> bc = (BeanCollection<?>) o;
if (!bc.isPopulated()) {
return null;
}
// For maps this is a collection of Map.Entry, otherwise it
// returns a collection of beans
return bc.getActualEntries();
}
if (o instanceof Map<?, ?>) {
// yes, we want the entrySet (to set the keys)
return ((Map<?, ?>) o).entrySet();
} else if (o instanceof Collection<?>) {
return ((Collection<?>) o);
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
}
@@ -39,6 +39,7 @@ import com.avaje.ebean.text.json.JsonWriteBeanVisitor;
import com.avaje.ebeaninternal.api.HashQueryPlan;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
@@ -681,10 +682,10 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return (queryUseCache != null) ? queryUseCache.booleanValue() : isBeanCaching();
}
public boolean calculateUseNaturalKeyCache(Boolean queryUseCache) {
return (queryUseCache != null) ? queryUseCache.booleanValue() : isBeanCaching();
public T cacheNaturalKey(SpiQuery<T> query, SpiTransaction t) {
return cacheHelp.naturalKeyLookup(query, t);
}
/**
* Return the cache options.
*/
@@ -820,13 +821,6 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
return cacheHelp.manyPropGet(parentId, propertyName);
}
/**
* Put the CachedManyIds into the cache.
*/
public void cacheManyPropPutEntry(Object parentId, String propertyName, CachedManyIds ids) {
cacheHelp.manyPropPutEntry(parentId, propertyName, ids);
}
/**
* Clear the bean cache.
*/
@@ -844,12 +838,12 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
public void cacheBeanPutData(EntityBean bean) {
cacheHelp.beanCachePut(bean);
}
/**
* Return a bean from the bean cache.
* Return a bean from the bean cache (or null).
*/
public T cacheBeanGet(Object id, Boolean readOnly) {
return cacheHelp.beanCacheGet(id, readOnly);
public T cacheBeanGet(SpiQuery<T> query, PersistenceContext context) {
return cacheHelp.beanCacheGet(query, context);
}
/**
@@ -859,24 +853,27 @@ public class BeanDescriptor<T> implements MetaBeanInfo {
cacheHelp.beanCacheRemove(id);
}
/**
* Returns true if it managed to populate/load the bean from the cache.
*/
public boolean cacheBeanLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) {
return cacheHelp.beanCacheLoad(bean, ebi, id);
}
/**
* Returns true if it managed to populate/load the bean from the cache.
*/
public boolean cacheBeanLoad(EntityBeanIntercept ebi) {
EntityBean bean = ebi.getOwner();
Object id = getId(bean);
return cacheBeanLoad(bean, ebi, id);
}
public boolean cacheIsNaturalKey(String propName) {
return cacheHelp.isNaturalKey(propName);
}
public Object cacheNaturalKeyLookup(Object uniqueKeyValue) {
return cacheHelp.naturalKeyLookup(uniqueKeyValue);
/**
* Try to hit the cache using the natural key.
*/
public T cacheNaturalKeyLookup(SpiQuery<T> query, SpiTransaction t) {
return cacheHelp.naturalKeyLookup(query, t);
}
/**
@@ -15,6 +15,8 @@ import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.cache.ServerCache;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD;
import com.avaje.ebeaninternal.server.cache.CachedBeanData;
import com.avaje.ebeaninternal.server.cache.CachedBeanDataFromBean;
@@ -23,6 +25,9 @@ import com.avaje.ebeaninternal.server.cache.CachedBeanDataUpdate;
import com.avaje.ebeaninternal.server.cache.CachedManyIds;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.loadcontext.DLoadContext;
import com.avaje.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
/**
* Helper for BeanDescriptor that manages the bean, query and collection caches.
@@ -31,8 +36,12 @@ import com.avaje.ebeaninternal.server.core.PersistRequestBean;
*/
public final class BeanDescriptorCacheHelp<T> {
private static final Logger logger = LoggerFactory.getLogger(BeanDescriptorCacheHelp.class);
public static final Logger queryLog = LoggerFactory.getLogger("org.avaje.ebean.cache.QUERY");
public static final Logger beanLog = LoggerFactory.getLogger("org.avaje.ebean.cache.BEAN");
public static final Logger manyLog = LoggerFactory.getLogger("org.avaje.ebean.cache.COLL");
public static final Logger natLog = LoggerFactory.getLogger("org.avaje.ebean.cache.NATKEY");
private final BeanDescriptor<T> desc;
private final ServerCacheManager cacheManager;
@@ -46,6 +55,8 @@ public final class BeanDescriptorCacheHelp<T> {
private final Class<T> beanType;
private final String cacheName;
private final BeanPropertyAssocOne<?>[] propertiesOneImported;
private ServerCache beanCache;
@@ -57,6 +68,7 @@ public final class BeanDescriptorCacheHelp<T> {
this.desc = desc;
this.beanType = desc.getBeanType();
this.cacheName = beanType.getSimpleName();
this.cacheManager = cacheManager;
this.cacheOptions = cacheOptions;
this.cacheSharableBeans = cacheSharableBeans;
@@ -89,9 +101,8 @@ public final class BeanDescriptorCacheHelp<T> {
query.setReadOnly(true);
query.setLoadBeanCache(true);
List<?> list = query.findList();
if (logger.isInfoEnabled()) {
String msg = "Loaded " + beanType + " cache with [" + list.size() + "] beans";
logger.info(msg);
if (beanLog.isInfoEnabled()) {
beanLog.info("Loaded {} cache with [{}] beans", cacheName, list.size());
}
}
}
@@ -135,6 +146,9 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public void queryCacheClear() {
if (queryCache != null) {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" CLEAR {}", cacheName);
}
queryCache.clear();
}
}
@@ -159,17 +173,26 @@ public final class BeanDescriptorCacheHelp<T> {
if (queryCache == null) {
queryCache = cacheManager.getQueryCache(beanType);
}
if (queryLog.isDebugEnabled()) {
queryLog.debug(" PUT {} {}", cacheName, id);
}
queryCache.put(id, query);
}
public void manyPropRemove(Object parentId, String propertyName) {
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
if (manyLog.isDebugEnabled()) {
manyLog.debug(" REMOVE {}({}).{}", cacheName, parentId, propertyName);
}
collectionIdsCache.remove(parentId);
}
public void manyPropClear(String propertyName) {
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
if (manyLog.isDebugEnabled()) {
manyLog.debug(" CLEAR {}(*).{} ", cacheName, propertyName);
}
collectionIdsCache.clear();
}
@@ -178,15 +201,15 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public CachedManyIds manyPropGet(Object parentId, String propertyName) {
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
return (CachedManyIds) collectionIdsCache.get(parentId);
}
/**
* Put the CachedManyIds into the cache.
*/
public void manyPropPutEntry(Object parentId, String propertyName, CachedManyIds ids) {
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName);
collectionIdsCache.put(parentId, ids);
CachedManyIds entry = (CachedManyIds) collectionIdsCache.get(parentId);
if (entry == null) {
if (manyLog.isTraceEnabled()) {
manyLog.trace(" GET {}({}).{} - cache miss", cacheName, parentId, propertyName);
}
} else if (manyLog.isDebugEnabled()) {
manyLog.debug(" GET {}({}).{} - hit", cacheName, parentId, propertyName);
}
return entry;
}
/**
@@ -194,19 +217,19 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId, Boolean readOnly) {
CachedManyIds ids = manyPropGet(parentId, many.getName());
if (ids == null) {
CachedManyIds entry = manyPropGet(parentId, many.getName());
if (entry == null) {
// not in cache so return unsuccessful
return false;
}
Object ownerBean = bc.getOwnerBean();
EntityBeanIntercept ebi = ((EntityBean) ownerBean)._ebean_getIntercept();
PersistenceContext persistenceContext = ebi.getPersistenceContext();
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
List<Object> idList = ids.getIdList();
List<Object> idList = entry.getIdList();
bc.checkEmptyLazyLoad();
for (int i = 0; i < idList.size(); i++) {
Object id = idList.get(i);
@@ -223,33 +246,101 @@ public final class BeanDescriptorCacheHelp<T> {
/**
* Put the beanCollection into the cache.
*/
public void manyPropPut(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, Object parentId) {
public void manyPropPut(BeanPropertyAssocMany<?> many, Object details, Object parentId) {
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
ArrayList<Object> idList = new ArrayList<Object>();
// get the underlying collection of beans (in the List, Set or Map)
Collection<?> actualDetails = bc.getActualDetails();
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
for (Object bean : actualDetails) {
// Collect the id values
idList.add(targetDescriptor.getId((EntityBean) bean));
}
CachedManyIds ids = new CachedManyIds(idList);
manyPropPutEntry(parentId, many.getName(), ids);
CachedManyIds entry = new CachedManyIds(idList);
ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, many.getName());
if (manyLog.isDebugEnabled()) {
manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, many.getName(), entry);
}
collectionIdsCache.put(parentId, entry);
}
public boolean isNaturalKey(String propName) {
public T naturalKeyLookup(SpiQuery<T> query, SpiTransaction t) {
if (!isNaturalKeyCaching(query.isUseBeanCache())) {
// no natural key caching for this query
return null;
}
// check if it is a find by unique id (using the natural key)
NaturalKeyBindParam keyBindParam = query.getNaturalKeyBindParam();
if (keyBindParam == null || !isNaturalKey(keyBindParam.getName())) {
// query is not appropriate
return null;
}
// try to lookup the id using the natural key
Object id = naturalKeyCache.get(keyBindParam.getValue());
if (natLog.isTraceEnabled()) {
natLog.trace(" LOOKUP {}({}) - id:{}", cacheName, keyBindParam.getValue(), id);
}
if (id == null) {
return null;
}
// try looking up into the bean cache using the id
T cacheBean = beanCacheGetInternal(id, query.isReadOnly());
if (cacheBean != null) {
setupContext(cacheBean, query, getPersistenceContext(t));
}
return cacheBean;
}
private PersistenceContext getPersistenceContext(SpiTransaction t) {
PersistenceContext context = null;
if (t == null) {
t = desc.getEbeanServer().getCurrentServerTransaction();
}
if (t != null) {
context = t.getPersistenceContext();
}
return context;
}
private boolean isNaturalKeyCaching(Boolean queryUseCache) {
return naturalKeyCache != null && (queryUseCache == null || queryUseCache.booleanValue());
}
private boolean isNaturalKey(String propName) {
return propName != null && propName.equals(cacheOptions.getNaturalKey());
}
public Object naturalKeyLookup(Object uniqueKeyValue) {
if (naturalKeyCache != null) {
return naturalKeyCache.get(uniqueKeyValue);
}
return null;
}
/**
* For a bean built from the cache this sets up its persistence context for future lazy loading etc.
*/
private void setupContext(Object bean, SpiQuery<T> query, PersistenceContext context) {
if (context == null) {
context = new DefaultPersistenceContext();
}
context.put(query.getId(), bean);
DLoadContext loadContext = new DLoadContext(desc.getEbeanServer(), desc, query.isReadOnly(), query);
loadContext.setPersistenceContext(context);
EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();
ebi.setPersistenceContext(context);
loadContext.register(null, ebi);
}
/**
* Return the beanCache creating it if necessary.
*/
private ServerCache getBeanCache() {
if (beanCache == null) {
beanCache = cacheManager.getBeanCache(beanType);
@@ -262,6 +353,9 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public void beanCacheClear() {
if (beanCache != null) {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" CLEAR {}", cacheName);
}
beanCache.clear();
}
}
@@ -274,10 +368,17 @@ public final class BeanDescriptorCacheHelp<T> {
CachedBeanData beanData = CachedBeanDataFromBean.extract(desc, bean);
Object id = desc.getId(bean);
if (beanLog.isDebugEnabled()) {
beanLog.debug(" PUT {}({})", cacheName, id);
}
getBeanCache().put(id, beanData);
if (beanData.isNaturalKeyUpdate() && naturalKeyCache != null) {
Object naturalKey = beanData.getNaturalKey();
if (naturalKey != null) {
if (natLog.isDebugEnabled()) {
natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id);
}
naturalKeyCache.put(naturalKey, id);
}
}
@@ -287,19 +388,33 @@ public final class BeanDescriptorCacheHelp<T> {
return (CachedBeanData) getBeanCache().get(id);
}
public T beanCacheGet(SpiQuery<T> query, PersistenceContext context) {
T bean = beanCacheGetInternal(query.getId(), query.isReadOnly());
if (bean != null) {
setupContext(bean, query, context);
}
return bean;
}
/**
* Return a bean from the bean cache.
*/
@SuppressWarnings("unchecked")
public T beanCacheGet(Object id, Boolean readOnly) {
private T beanCacheGetInternal(Object id, Boolean readOnly) {
CachedBeanData d = (CachedBeanData) getBeanCache().get(id);
if (d == null) {
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET {}({}) - cache miss", cacheName, id);
}
return null;
}
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
Object bean = d.getSharableBean();
if (bean != null) {
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET {}({}) - hit shared bean", cacheName, id);
}
return (T) bean;
}
}
@@ -311,6 +426,9 @@ public final class BeanDescriptorCacheHelp<T> {
}
CachedBeanDataToBean.load(desc, bean, d);
if (beanLog.isTraceEnabled()) {
beanLog.trace(" GET {}({}) - hit", cacheName, id);
}
return (T) bean;
}
@@ -320,6 +438,9 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public void beanCacheRemove(Object id) {
if (beanCache != null) {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" REMOVE {}({})", cacheName, id);
}
beanCache.remove(id);
}
for (int i = 0; i < propertiesOneImported.length; i++) {
@@ -327,18 +448,30 @@ public final class BeanDescriptorCacheHelp<T> {
}
}
/**
* Returns true if it managed to populate/load the bean from the cache.
*/
public boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) {
CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id);
if (cacheData == null) {
if (beanLog.isTraceEnabled()) {
beanLog.trace(" LOAD {}({}) - cache miss", cacheName, id);
}
return false;
}
int lazyLoadProperty = ebi.getLazyLoadPropertyIndex();
if (lazyLoadProperty > -1 && !cacheData.containsProperty(lazyLoadProperty)) {
if (lazyLoadProperty > -1 && !cacheData.isLoaded(lazyLoadProperty)) {
if (beanLog.isTraceEnabled()) {
beanLog.trace(" LOAD {}({}) - cache miss on property", cacheName, id);
}
return false;
}
CachedBeanDataToBean.load(desc, bean, cacheData);
if (beanLog.isDebugEnabled()) {
beanLog.debug(" LOAD {}({}) - hit", cacheName, id);
}
return true;
}
@@ -347,9 +480,15 @@ public final class BeanDescriptorCacheHelp<T> {
*/
public void handleDelete(Object id, PersistRequestBean<T> deleteRequest) {
if (queryCache != null) {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" CLEAR {}(*) - delete trigger", cacheName);
}
queryCache.clear();
}
if (beanCache != null) {
if (beanLog.isDebugEnabled()) {
beanLog.debug(" REMOVE {}({})", cacheName, id);
}
beanCache.remove(id);
}
for (int i = 0; i < propertiesOneImported.length; i++) {
@@ -362,6 +501,9 @@ public final class BeanDescriptorCacheHelp<T> {
public void handleInsert(Object id, PersistRequestBean<T> insertRequest) {
if (queryCache != null) {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" CLEAR {}(*) - insert trigger", cacheName);
}
queryCache.clear();
}
for (int i = 0; i < propertiesOneImported.length; i++) {
@@ -375,26 +517,73 @@ public final class BeanDescriptorCacheHelp<T> {
public void handleUpdate(Object id, PersistRequestBean<T> updateRequest) {
if (queryCache != null) {
if (queryLog.isDebugEnabled()) {
queryLog.debug(" CLEAR {}(*) - update trigger", cacheName);
}
queryCache.clear();
}
ServerCache cache = getBeanCache();
CachedBeanData existingData = (CachedBeanData) cache.get(id);
if (existingData != null) {
CachedBeanData newData = CachedBeanDataUpdate.update(desc, existingData, updateRequest.getEntityBean());
cache.put(id, newData);
if (newData.isNaturalKeyUpdate() && naturalKeyCache != null) {
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
// clear the appropriate manyProp caches first
for (int i = 0; i < manyCollections.size(); i++) {
manyPropRemove(id, manyCollections.get(i).getName());
}
}
// check if the bean itself was updated
if (!updateRequest.isUpdatedManysOnly()) {
// update the bean cache entry if it exists
ServerCache cache = getBeanCache();
CachedBeanData existingData = (CachedBeanData) cache.get(id);
if (existingData != null) {
Object oldKey = newData.getOldNaturalKey();
Object newKey = newData.getNaturalKey();
if (oldKey != null) {
naturalKeyCache.remove(oldKey);
}
if (newKey != null) {
naturalKeyCache.put(newKey, id);
if (isCachedDataTooOld(existingData)) {
// just remove the entry from the cache
if (beanLog.isDebugEnabled()) {
beanLog.debug(" REMOVE {}({}) - entry too old", cacheName, id);
}
cache.remove(id);
} else {
// Update the cache data with the changes from our update
CachedBeanData newData = CachedBeanDataUpdate.update(desc, existingData, updateRequest.getEntityBean());
if (beanLog.isDebugEnabled()) {
beanLog.debug(" UPDATE {}({})", cacheName, id);
}
cache.put(id, newData);
if (newData.isNaturalKeyUpdate() && naturalKeyCache != null) {
Object oldKey = newData.getOldNaturalKey();
Object newKey = newData.getNaturalKey();
if (natLog.isDebugEnabled()) {
natLog.debug(".. update {} PUT({}, {}) REMOVE({})", cacheName, newKey, id, oldKey);
}
if (oldKey != null) {
naturalKeyCache.remove(oldKey);
}
if (newKey != null) {
naturalKeyCache.put(newKey, id);
}
}
}
}
}
if (manyCollections != null) {
for (int i = 0; i < manyCollections.size(); i++) {
BeanPropertyAssocMany<?> many = manyCollections.get(i);
Object manyValue = many.getValue(updateRequest.getEntityBean());
manyPropPut(many, manyValue, id);
}
}
}
private boolean isCachedDataTooOld(CachedBeanData existingData) {
return cacheOptions.isTooOldInMillis(System.currentTimeMillis() - existingData.getWhenCreated());
}
/**
@@ -337,7 +337,7 @@ public class DeployBeanDescriptor<T> {
}
/**
* Return the reference options.
* Return the cache options.
*/
public CacheOptions getCacheOptions() {
return cacheOptions;
@@ -8,6 +8,7 @@ import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import com.avaje.ebean.annotation.CacheStrategy;
import com.avaje.ebean.annotation.CacheTuning;
import com.avaje.ebean.annotation.EntityConcurrencyMode;
import com.avaje.ebean.annotation.NamedUpdate;
import com.avaje.ebean.annotation.NamedUpdates;
@@ -108,8 +109,9 @@ public class AnnotationClass extends AnnotationParser {
}
CacheStrategy cacheStrategy = cls.getAnnotation(CacheStrategy.class);
if (cacheStrategy != null) {
readCacheStrategy(cacheStrategy);
CacheTuning cacheTuning = cls.getAnnotation(CacheTuning.class);
if (cacheStrategy != null || cacheTuning != null) {
readCacheStrategy(cacheStrategy, cacheTuning);
}
EntityConcurrencyMode entityConcurrencyMode = cls.getAnnotation(EntityConcurrencyMode.class);
@@ -118,18 +120,24 @@ public class AnnotationClass extends AnnotationParser {
}
}
private void readCacheStrategy(CacheStrategy cacheStrategy) {
private void readCacheStrategy(CacheStrategy cacheStrategy, CacheTuning cacheTuning) {
CacheOptions cacheOptions = descriptor.getCacheOptions();
cacheOptions.setUseCache(cacheStrategy.useBeanCache());
cacheOptions.setReadOnly(cacheStrategy.readOnly());
cacheOptions.setWarmingQuery(cacheStrategy.warmingQuery());
if (cacheStrategy.naturalKey().length() > 0) {
String propName = cacheStrategy.naturalKey().trim();
DeployBeanProperty beanProperty = descriptor.getBeanProperty(propName);
if (beanProperty != null) {
beanProperty.setNaturalKey(true);
cacheOptions.setNaturalKey(propName);
if (cacheTuning != null) {
cacheOptions.setMaxSecsToLive(cacheTuning.maxSecsToLive());
cacheOptions.setMaxIdleSecs(cacheTuning.maxIdleSecs());
}
if (cacheStrategy != null) {
cacheOptions.setUseCache(cacheStrategy.useBeanCache());
cacheOptions.setReadOnly(cacheStrategy.readOnly());
cacheOptions.setWarmingQuery(cacheStrategy.warmingQuery());
if (cacheStrategy.naturalKey().length() > 0) {
String propName = cacheStrategy.naturalKey().trim();
DeployBeanProperty beanProperty = descriptor.getBeanProperty(propName);
if (beanProperty != null) {
beanProperty.setNaturalKey(true);
cacheOptions.setNaturalKey(propName);
}
}
}
}
@@ -32,6 +32,7 @@ import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate;
import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql;
import com.avaje.ebeaninternal.server.core.Persister;
import com.avaje.ebeaninternal.server.core.PstmtBatch;
import com.avaje.ebeaninternal.server.deploy.BeanCollectionUtil;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
import com.avaje.ebeaninternal.server.deploy.BeanManager;
@@ -230,6 +231,8 @@ public final class DefaultPersister implements Persister {
saveAssocMany(false, request);
intercept.setReference(-1);
}
request.checkUpdatedManysOnly();
} else {
if (request.isInsert()) {
@@ -299,6 +302,9 @@ public final class DefaultPersister implements Persister {
// save all the beans in assocMany's after
saveAssocMany(false, request);
}
request.checkUpdatedManysOnly();
} finally {
request.unRegisterBean();
}
@@ -568,6 +574,9 @@ public final class DefaultPersister implements Persister {
// check that property is loaded and not empty uninitialised collection
if (request.isLoadedProperty(manys[i]) && !manys[i].isEmptyBeanCollection(parentBean)) {
saveMany(new SaveManyPropRequest(insertedParent, manys[i], parentBean, request));
if (!insertedParent) {
request.addUpdatedManyProperty(manys[i]);
}
}
}
}
@@ -580,7 +589,7 @@ public final class DefaultPersister implements Persister {
private final boolean insertedParent;
private final BeanPropertyAssocMany<?> many;
private final EntityBean parentBean;
private final SpiTransaction t;
private final SpiTransaction transaction;
private final boolean cascade;
private final boolean statelessUpdate;
private final boolean deleteMissingChildren;
@@ -590,7 +599,7 @@ public final class DefaultPersister implements Persister {
this.many = many;
this.cascade = many.getCascadeInfo().isSave();
this.parentBean = parentBean;
this.t = request.getTransaction();
this.transaction = request.getTransaction();
this.statelessUpdate = request.isStatelessUpdate();
this.deleteMissingChildren = request.isDeleteMissingChildren();
}
@@ -599,14 +608,14 @@ public final class DefaultPersister implements Persister {
this.insertedParent = false;
this.many = many;
this.parentBean = parentBean;
this.t = t;
this.transaction = t;
this.cascade = true;
this.statelessUpdate = false;
this.deleteMissingChildren = false;
}
public boolean isSaveIntersection() {
return t.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName());
return transaction.isSaveAssocManyIntersection(many.getIntersectionTableJoin().getTable(), many.getBeanDescriptor().getName());
}
private Object getValue() {
@@ -638,7 +647,7 @@ public final class DefaultPersister implements Persister {
}
private SpiTransaction getTransaction() {
return t;
return transaction;
}
private boolean isCascade() {
@@ -714,7 +723,7 @@ public final class DefaultPersister implements Persister {
// check that the list is not null and if it is a BeanCollection
// check that is has been populated (don't trigger lazy loading)
// For a Map this is a collection of Map.Entry objects and not beans
Collection<?> collection = getActualEntries(details);
Collection<?> collection = BeanCollectionUtil.getActualEntries(details);
if (collection == null) {
// nothing to do here
@@ -1182,33 +1191,7 @@ public final class DefaultPersister implements Persister {
}
}
/**
* Return the details of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
private Collection<?> getActualEntries(Object o) {
if (o == null) {
return null;
}
if (o instanceof BeanCollection<?>) {
BeanCollection<?> bc = (BeanCollection<?>) o;
if (!bc.isPopulated()) {
return null;
}
// For maps this is a collection of Map.Entry, otherwise it
// returns a collection of beans
return bc.getActualEntries();
}
if (o instanceof Map<?, ?>) {
// yes, we want the entrySet (to set the keys)
return ((Map<?, ?>) o).entrySet();
} else if (o instanceof Collection<?>) {
return ((Collection<?>) o);
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
/**
* Create the Persist Request Object that wraps all the objects used to