mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Caching changes and refactoring, includes Fix for Wobu - TestCacheCollectionIds test case
This commit is contained in:
@@ -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];
|
||||
}
|
||||
|
||||
+1
-7
@@ -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();
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user