diff --git a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java index 1ca3fda7a..1ddafd046 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/api/LoadBeanRequest.java @@ -158,7 +158,7 @@ public class LoadBeanRequest extends LoadRequest { EntityBean loadedBean = (EntityBean) list.get(i); loadedIds.add(desc.getId(loadedBean)); if (isLoadCache()) { - desc.cacheBeanPutData(loadedBean); + desc.cacheBeanPut(loadedBean); } } diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java index 209c71a96..9b76156b7 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.api; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -105,20 +106,17 @@ public class TransactionEvent implements Serializable { } /** - * Notify the cache of bean changes. - *

- * This returns the TransactionEventTable so that if any - * general table changes can also be used to invalidate - * parts of the cache. - *

+ * Build and return the cache changeSet. */ - public void notifyCache() { + public CacheChangeSet buildCacheChanges() { + CacheChangeSet changeSet = new CacheChangeSet(); if (eventBeans != null) { - eventBeans.notifyCache(); + eventBeans.notifyCache(changeSet); } if (deleteByIdMap != null) { - deleteByIdMap.notifyCache(); + deleteByIdMap.notifyCache(changeSet); } + return changeSet; } /** diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java index 612dcb153..4bfd565ae 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java +++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEventBeans.java @@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.api; import java.util.ArrayList; import java.util.List; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeaninternal.server.core.PersistRequestBean; /** @@ -30,10 +31,13 @@ public class TransactionEventBeans { requests.add(request); } - - public void notifyCache() { + + /** + * Collect the cache changes. + */ + public void notifyCache(CacheChangeSet changeSet) { for (int i = 0; i < requests.size(); i++) { - requests.get(i).notifyCache(); + requests.get(i).notifyCache(changeSet); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChange.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChange.java new file mode 100644 index 000000000..9c29534dc --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChange.java @@ -0,0 +1,13 @@ +package com.avaje.ebeaninternal.server.cache; + +/** + * A change to the cache. + */ +public interface CacheChange { + + /** + * Apply the change. + */ + void apply(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanRemove.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanRemove.java new file mode 100644 index 000000000..df8ce0569 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanRemove.java @@ -0,0 +1,23 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Change to remove bean from L2 cache. + */ +class CacheChangeBeanRemove implements CacheChange { + + private final BeanDescriptor descriptor; + + private final Object id; + + CacheChangeBeanRemove(BeanDescriptor descriptor, Object id) { + this.descriptor = descriptor; + this.id = id; + } + + @Override + public void apply() { + descriptor.cacheHandleDeleteById(id); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanUpdate.java new file mode 100644 index 000000000..38442c9a9 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeBeanUpdate.java @@ -0,0 +1,30 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +import java.util.Map; + +/** + * Put a new bean entry into the cache. + */ +class CacheChangeBeanUpdate implements CacheChange { + + private final BeanDescriptor desc; + private final Object id; + private final Map changes; + private final boolean updateNaturalKey; + private final long version; + + CacheChangeBeanUpdate(BeanDescriptor desc, Object id, Map changes, boolean updateNaturalKey, long version) { + this.desc = desc; + this.id = id; + this.changes = changes; + this.updateNaturalKey = updateNaturalKey; + this.version = version; + } + + @Override + public void apply() { + desc.cacheBeanUpdate(id, changes, updateNaturalKey, version); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeNaturalKeyPut.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeNaturalKeyPut.java new file mode 100644 index 000000000..3cad5c356 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeNaturalKeyPut.java @@ -0,0 +1,24 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Change the natural key mapping for a bean. + */ +class CacheChangeNaturalKeyPut implements CacheChange { + + private final BeanDescriptor descriptor; + private final Object id; + private final Object newKey; + + CacheChangeNaturalKeyPut(BeanDescriptor descriptor, Object id, Object newKey) { + this.descriptor = descriptor; + this.id = id; + this.newKey = newKey; + } + + @Override + public void apply() { + descriptor.cacheNaturalKeyPut(id, newKey); + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java new file mode 100644 index 000000000..1c3c8fa7f --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java @@ -0,0 +1,196 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * List of changes to be applied to L2 cache. + */ +public class CacheChangeSet { + + private final List entries = new ArrayList(); + + private final Set queryCaches = new HashSet(); + + private final Map manyChangeMap = new HashMap(); + + /** + * Apply all the changes to the L2 cache. + */ + public void apply() { + for (BeanDescriptor entry : queryCaches) { + entry.queryCacheClear(); + } + for (CacheChange entry : entries) { + entry.apply(); + } + for (CacheChange entry : manyChangeMap.values()) { + entry.apply(); + } + } + + /** + * Add an entry to clear a query cache. + */ + public void addClearQuery(BeanDescriptor descriptor) { + queryCaches.add(descriptor); + } + + /** + * Add many property clear. + */ + public void addManyClear(BeanDescriptor desc, String manyProperty) { + many(desc, manyProperty).setClear(); + } + + /** + * Add many property remove. + */ + public void addManyRemove(BeanDescriptor desc, String manyProperty, Object parentId) { + many(desc, manyProperty).addRemove(parentId); + } + + /** + * Add many property put. + */ + public void addManyPut(BeanDescriptor desc, String manyProperty, Object parentId, CachedManyIds entry) { + many(desc, manyProperty).addPut(parentId, entry); + } + + /** + * Remove a bean from the cache. + */ + public void addBeanRemove(BeanDescriptor desc, Object id) { + entries.add(new CacheChangeBeanRemove(desc, id)); + } + + /** + * Update a bean entry. + */ + public void addBeanUpdate(BeanDescriptor desc, Object id, Map changes, boolean updateNaturalKey, long version) { + entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version)); + } + + /** + * Update a natural key. + */ + public void addNaturalKeyPut(BeanDescriptor desc, Object id, Object val) { + entries.add(new CacheChangeNaturalKeyPut(desc, id, val)); + } + + /** + * Return the ManyChange for the given descriptor and property manyProperty. + */ + private ManyChange many(BeanDescriptor desc, String manyProperty) { + ManyKey key = new ManyKey(desc, manyProperty); + ManyChange manyChange = manyChangeMap.get(key); + if (manyChange == null) { + manyChange = new ManyChange(key); + manyChangeMap.put(key, manyChange); + } + return manyChange; + } + + /** + * Changes for a specific many property. + */ + private static class ManyChange implements CacheChange { + + final ManyKey key; + + final List removes = new ArrayList(); + + final Map puts = new LinkedHashMap(); + + boolean clear; + + ManyChange(ManyKey key) { + this.key = key; + } + + /** + * Clear all entries. + */ + void setClear() { + this.clear = true; + removes.clear(); + } + + /** + * Remove entry for the given parentId. + */ + void addRemove(Object parentId) { + if (!clear) { + removes.add(parentId); + } + } + + /** + * Put entry for the given parentId. + */ + void addPut(Object parentId, CachedManyIds entry) { + puts.put(parentId, entry); + } + + @Override + public void apply() { + if (clear) { + key.cacheClear(); + } else { + for (Map.Entry entry : puts.entrySet()) { + key.cachePut(entry.getKey(), entry.getValue()); + } + for (Object parentId : removes) { + key.cacheRemove(parentId); + } + } + } + } + + /** + * Key for changes on a many property. + */ + private static class ManyKey { + + private final BeanDescriptor desc; + + private final String manyProperty; + + ManyKey(BeanDescriptor desc, String manyProperty) { + this.desc = desc; + this.manyProperty = manyProperty; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ManyKey manyKey = (ManyKey) o; + return desc.equals(manyKey.desc) && manyProperty.equals(manyKey.manyProperty); + } + + @Override + public int hashCode() { + return 31 * desc.hashCode() + manyProperty.hashCode(); + } + + void cacheClear() { + desc.cacheManyPropClear(manyProperty); + } + + void cachePut(Object parentId, CachedManyIds entry) { + desc.cacheManyPropPut(manyProperty, parentId, entry); + } + + void cacheRemove(Object parentId) { + desc.cacheManyPropRemove(manyProperty, parentId); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java index 8bee85e3f..8b35cd2b5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanData.java @@ -1,66 +1,92 @@ package com.avaje.ebeaninternal.server.cache; -import java.io.Serializable; -import java.util.Arrays; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; /** * Data held in the bean cache for cached beans. */ -public class CachedBeanData implements Serializable { +public class CachedBeanData implements Externalizable { - private final long whenCreated; - private final Object sharableBean; - private final boolean[] loaded; - private final Object[] data; + private long whenCreated; + private long version; + private String discValue; + private Map data; - private final boolean naturalKeyUpdate; - private final Object naturalKey; - private final Object oldNaturalKey; + /** + * The sharable bean is effectively transient (near cache only). + */ + private transient Object sharableBean; - public CachedBeanData(Object sharableBean, boolean[] loaded, Object[] data, Object naturalKey, Object oldNaturalKey) { + /** + * Construct from a loaded bean. + */ + public CachedBeanData(Object sharableBean, String discValue, Map data, long version) { this.whenCreated = System.currentTimeMillis(); this.sharableBean = sharableBean; - this.loaded = loaded; + this.discValue = discValue; this.data = data; - this.naturalKeyUpdate = naturalKey != null; - this.naturalKey = (naturalKey != null) ? naturalKey : oldNaturalKey; - this.oldNaturalKey = oldNaturalKey; + this.version = version; + } + + /** + * Construct from serialisation. + */ + public CachedBeanData() { + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeLong(version); + out.writeLong(whenCreated); + boolean hasDisc = discValue != null; + out.writeBoolean(hasDisc); + if (hasDisc) { + out.writeUTF(discValue); + } + out.writeInt(data.size()); + for (Map.Entry entry : data.entrySet()) { + out.writeUTF(entry.getKey()); + out.writeObject(entry.getValue()); + } + } + + @Override + @SuppressWarnings("unchecked") + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + version = in.readLong(); + whenCreated = in.readLong(); + if (in.readBoolean()) { + discValue = in.readUTF(); + } + data = new LinkedHashMap(); + int count = in.readInt(); + for (int i = 0; i < count; i++) { + String key = in.readUTF(); + Object val = in.readObject(); + data.put(key, val); + } } public String toString() { - return Arrays.toString(data); + return data.toString(); } /** - * Return a copy of the property data. + * Create and return a new version of CachedBeanData based on this + * entry applying the given changes. */ - public Object[] copyData() { - Object[] dest = new Object[data.length]; - System.arraycopy(data, 0, dest, 0, data.length); - return dest; - } + public CachedBeanData update(Map changes, long version) { - /** - * Return a copy of the loaded status for the properties. - */ - public boolean[] copyLoaded() { - boolean[] dest = new boolean[data.length]; - System.arraycopy(loaded, 0, dest, 0, dest.length); - return dest; - } - - /** - * Return the loaded status for each property. - */ - public boolean[] getLoaded() { - return loaded; - } - - /** - * Return the property values. - */ - public Object[] getData() { - return data; + Map copy = new HashMap(); + copy.putAll(data); + copy.putAll(changes); + return new CachedBeanData(null, discValue, copy, version); } /** @@ -71,45 +97,38 @@ public class CachedBeanData implements Serializable { } /** - * Return a sharable (immutable read only) bean. + * Return the version value. + */ + public long getVersion() { + return version; + } + + /** + * Return the raw discriminator value. + */ + public String getDiscValue() { + return discValue; + } + + /** + * Return a sharable (immutable read only) bean. Near cache only use. */ public Object getSharableBean() { return sharableBean; } /** - * Return true if this data requires an update to the natural key cache. + * Return true if the property is held. */ - public boolean isNaturalKeyUpdate() { - return naturalKeyUpdate; + public boolean isLoaded(String propertyName) { + return data.containsKey(propertyName); } /** - * Return the new/current natural key value. + * Return the value for a given property name. */ - public Object getNaturalKey() { - return naturalKey; - } - - /** - * Return the old natural key (its entry should be removed). - */ - public Object getOldNaturalKey() { - return oldNaturalKey; - } - - /** - * 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]; + public Object getData(String propertyName) { + return data.get(propertyName); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java index bdade5923..387552f37 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBean.java @@ -5,6 +5,9 @@ import com.avaje.ebean.bean.EntityBeanIntercept; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import java.util.LinkedHashMap; +import java.util.Map; + public class CachedBeanDataFromBean { @@ -12,38 +15,28 @@ public class CachedBeanDataFromBean { EntityBeanIntercept ebi = bean._ebean_getIntercept(); - Object[] data = new Object[desc.getPropertyCount()]; - boolean[] loaded = new boolean[desc.getPropertyCount()]; + Map data = new LinkedHashMap(); BeanProperty idProperty = desc.getIdProperty(); if (idProperty != null) { int propertyIndex = idProperty.getPropertyIndex(); if (ebi.isLoadedProperty(propertyIndex)) { - // extract the id property value - data[propertyIndex] = idProperty.getCacheDataValue(bean); - loaded[propertyIndex] = true; + data.put(idProperty.getName(), idProperty.getCacheDataValue(bean)); } } BeanProperty[] props = desc.propertiesNonMany(); - Object naturalKey = null; - // extract all the non-many properties for (int i = 0; i < props.length; i++) { BeanProperty prop = props[i]; if (ebi.isLoadedProperty(prop.getPropertyIndex())) { - int propertyIndex = prop.getPropertyIndex(); - data[propertyIndex] = prop.getCacheDataValue(bean); - loaded[propertyIndex] = true; - if (prop.isNaturalKey()) { - naturalKey = prop.getValue(bean); - } + data.put(prop.getName(), prop.getCacheDataValue(bean)); } } + long version = desc.getVersion(bean); EntityBean sharableBean = createSharableBean(desc, bean, ebi); - - return new CachedBeanData(sharableBean, loaded, data, naturalKey, null); + return new CachedBeanData(sharableBean, desc.getDiscValue(), data, version); } private static EntityBean createSharableBean(BeanDescriptor desc, EntityBean bean, EntityBeanIntercept beanEbi) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java index 9feaf5cb4..a3c60bb67 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataToBean.java @@ -25,9 +25,9 @@ public class CachedBeanDataToBean { loadProperty(bean, cacheBeanData, ebi, props[i]); } - BeanPropertyAssocMany[] manys = desc.propertiesMany(); - for (int i = 0; i < manys.length; i++) { - manys[i].createReferenceIfNull(bean); + BeanPropertyAssocMany[] many = desc.propertiesMany(); + for (int i = 0; i < many.length; i++) { + many[i].createReferenceIfNull(bean); } ebi.setLoadedLazy(); @@ -35,14 +35,10 @@ public class CachedBeanDataToBean { private static void loadProperty(EntityBean bean, CachedBeanData cacheBeanData, EntityBeanIntercept ebi, BeanProperty prop) { - int propertyIndex = prop.getPropertyIndex(); - if (cacheBeanData.isLoaded(propertyIndex)) { - //noinspection StatementWithEmptyBody - if (ebi.isLoadedProperty(propertyIndex)) { - // already loaded (lazy load on partially loaded bean) - } else { - Object data = cacheBeanData.getData(propertyIndex); - prop.setCacheDataValue(bean, data); + if (cacheBeanData.isLoaded(prop.getName())) { + if (!ebi.isLoadedProperty(prop.getPropertyIndex())) { + Object value = cacheBeanData.getData(prop.getName()); + prop.setCacheDataValue(bean, value); } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java deleted file mode 100644 index 7235e72a9..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataUpdate.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.avaje.ebeaninternal.server.cache; - -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.bean.EntityBeanIntercept; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanProperty; - -/** - * Create a new CachedBeanData based on the existing CachedBeanData and the updated bean. - */ -public class CachedBeanDataUpdate { - - /** - * Create a new CachedBeanData based on the existing CachedBeanData and the updated bean. - */ - public static CachedBeanData update(BeanDescriptor desc, CachedBeanData existingData, EntityBean updateBean) { - - // take a copy of the raw data and loaded status - boolean[] copyLoaded = existingData.copyLoaded(); - Object[] copyData = existingData.copyData(); - - EntityBeanIntercept ebi = updateBean._ebean_getIntercept(); - - Object newNaturalKey = null; - Object oldNaturalKey = existingData.getNaturalKey(); - - BeanProperty[] props = desc.propertiesNonMany(); - for (int i = 0; i < props.length; i++) { - // check if the properties was in the update - int propertyIndex = props[i].getPropertyIndex(); - if (ebi.isLoadedProperty(propertyIndex)) { - if (props[i].isNaturalKey()) { - newNaturalKey = updateBean._ebean_getField(propertyIndex); - } - // set the cache safe value for the property and mark it as loaded - copyData[propertyIndex] = props[i].getCacheDataValue(updateBean); - copyLoaded[propertyIndex] = true; - } - } - - return new CachedBeanData(null, copyLoaded, copyData, newNaturalKey, oldNaturalKey); - } - -} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 02a390349..31589cb4e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -13,6 +13,7 @@ import com.avaje.ebeaninternal.api.DerivedRelationshipData; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.api.TransactionEvent; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.BeanManager; import com.avaje.ebeaninternal.server.deploy.BeanProperty; @@ -134,6 +135,8 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ private boolean requestUpdateAllLoadedProps; + private long version; + public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager mgr, SpiTransaction t, PersistExecute persistExecute, PersistRequest.Type type, boolean saveRecurse, boolean publish) { @@ -298,38 +301,47 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return intercept.getDirtyValues(); } - public boolean isNotify() { + /** + * Set the cache notify status. + */ + public void setNotifyCache() { this.notifyCache = beanDescriptor.isCacheNotify(publish); + } + + /** + * Return true if this change should notify cache, listener or doc store. + */ + public boolean isNotify() { return notifyCache || isNotifyPersistListener() || isDocStoreNotify(); } /** - * Return true if this request should updateAdd an ElasticSearch index - * by queuing an event or direct updateAdd (via Bulk API). + * Return true if this request should update the document store. */ private boolean isDocStoreNotify() { return docStoreMode != DocStoreMode.IGNORE; } - public boolean isNotifyPersistListener() { + private boolean isNotifyPersistListener() { return beanPersistListener != null; } /** * Notify/Update the local L2 cache after the transaction has successfully committed. */ - public void notifyCache() { + public void notifyCache(CacheChangeSet changeSet) { if (notifyCache) { switch (type) { case INSERT: - beanDescriptor.cacheHandleInsert(this); + beanDescriptor.cacheHandleInsert(this, changeSet); break; case UPDATE: - beanDescriptor.cacheHandleUpdate(idValue, this); + beanDescriptor.cacheHandleUpdate(idValue, this, changeSet); break; case DELETE: case SOFT_DELETE: // Bean deleted from cache early via postDelete() + beanDescriptor.cacheHandleDelete(idValue, this, changeSet); break; default: throw new IllegalStateException("Invalid type " + type); @@ -714,7 +726,6 @@ public final class PersistRequestBean extends PersistRequest implements BeanP */ private void postDelete() { beanDescriptor.contextClear(transaction.getPersistenceContext(), idValue); - beanDescriptor.cacheHandleDelete(idValue, this); } private void changeLog() { @@ -734,8 +745,9 @@ public final class PersistRequestBean extends PersistRequest implements BeanP if (controller != null) { controllerPost(); } + setNotifyCache(); - if (type == Type.UPDATE && docStoreMode == DocStoreMode.UPDATE) { + if (type == Type.UPDATE && (notifyCache || docStoreMode == DocStoreMode.UPDATE)) { // get the dirty properties for update notification to the doc store dirtyProperties = intercept.getDirtyProperties(); } @@ -870,12 +882,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP * cache on post commit. */ public void addUpdatedManyProperty(BeanPropertyAssocMany updatedAssocMany) { - // if (notifyCache) { if (updatedManys == null) { updatedManys = new ArrayList>(5); } updatedManys.add(updatedAssocMany); - // } } /** @@ -897,6 +907,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP this.idValue = beanDescriptor.getId(entityBean); } updatedManysOnly = true; + setNotifyCache(); addEvent(); } } @@ -1003,7 +1014,13 @@ public final class PersistRequestBean extends PersistRequest implements BeanP * Set the value of the Version property on the bean. */ public void setVersionValue(Object versionValue) { - beanDescriptor.getVersionProperty().setValueIntercept(entityBean, versionValue); + version = beanDescriptor.setVersion(entityBean, versionValue); } + /** + * Return the version in long form (if set). + */ + public long getVersion() { + return version; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 6f281053a..641bd55d5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -41,7 +41,9 @@ 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.CacheChangeSet; import com.avaje.ebeaninternal.server.cache.CachedBeanData; +import com.avaje.ebeaninternal.server.cache.CachedManyIds; import com.avaje.ebeaninternal.server.core.CacheOptions; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.core.DiffHelp; @@ -210,12 +212,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Map of BeanProperty Linked so as to preserve order. */ - private final LinkedHashMap propMap; + protected final LinkedHashMap propMap; /** * The type of bean this describes. */ private final Class beanType; + protected final Class rootBeanType; /** @@ -226,8 +229,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final String[] properties; - private final int propertyCount; - /** * Intercept pre post on insert,update, and delete . */ @@ -337,6 +338,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { * All non transient properties excluding the id properties. */ private final BeanProperty[] propertiesNonTransient; + protected final BeanProperty[] propertiesIndex; /** * The bean class name or the table name for MapBeans. @@ -403,7 +405,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.serverName = owner.getServerName(); this.entityType = deploy.getEntityType(); this.properties = deploy.getProperties(); - this.propertyCount = this.properties.length; this.name = InternString.intern(deploy.getName()); this.baseTableAlias = "t0"; this.fullName = InternString.intern(deploy.getFullName()); @@ -512,12 +513,17 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { this.idPropertyIndex = -1; this.versionPropertyIndex = -1; this.unloadProperties = new int[0]; + this.propertiesIndex = new BeanProperty[0]; } else { EntityBeanIntercept ebi = prototypeEntityBean._ebean_getIntercept(); this.idPropertyIndex = (idProperty == null) ? -1 : ebi.findProperty(idProperty.getName()); this.versionPropertyIndex = (versionProperty == null) ? -1 : ebi.findProperty(versionProperty.getName()); this.unloadProperties = derivePropertiesToUnload(prototypeEntityBean); + this.propertiesIndex = new BeanProperty[ebi.getPropertyLength()]; + for (int i = 0; i < propertiesIndex.length; i++) { + propertiesIndex[i] = propMap.get(ebi.getProperty(i)); + } } } @@ -595,10 +601,6 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return entityType; } - public int getPropertyCount() { - return propertyCount; - } - public String[] getProperties() { return properties; } @@ -711,6 +713,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Initialise the document mapping. */ + @SuppressWarnings("unchecked") public void initialiseDocMapping() { for (int i = 0; i < propertiesMany.length; i++) { propertiesMany[i].initialisePostTarget(); @@ -1100,6 +1103,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { cacheHelp.queryCachePut(id, query); } + /** + * Add a query cache clear into the changeSet. + */ + public void queryCacheClear(CacheChangeSet changeSet) { + cacheHelp.queryCacheClear(changeSet); + } + /** * Try to load the beanCollection from cache return true if successful. */ @@ -1114,39 +1124,70 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { cacheHelp.manyPropPut(many, bc, parentId); } - public void cacheManyPropRemove(Object parentId, String propertyName) { - cacheHelp.manyPropRemove(parentId, propertyName); + /** + * Update the bean collection entry in the cache. + */ + public void cacheManyPropPut(String name, Object parentId, CachedManyIds entry) { + cacheHelp.cachePutManyIds(parentId, name, entry); + } + + public void cacheManyPropRemove(String propertyName, Object parentId) { + cacheHelp.manyPropRemove(propertyName, parentId); } public void cacheManyPropClear(String propertyName) { cacheHelp.manyPropClear(propertyName); } - public void cacheBeanPut(T bean) { - cacheBeanPutData((EntityBean) bean); + /** + * Extract the raw cache data from the embedded bean. + */ + public CachedBeanData cacheEmbeddedBeanExtract(EntityBean bean) { + return cacheHelp.beanExtractData(this, bean); } /** - * Extract the raw cache data from the bean. + * Load the embedded bean (taking into account inheritance). */ - public CachedBeanData cacheBeanExtractData(EntityBean bean) { - return cacheHelp.beanExtractData(bean); - } - - /** - * Load the raw cache data into the bean. - */ - public void cacheBeanLoadData(EntityBean bean, CachedBeanData data) { - cacheHelp.beanLoadData(bean, data); + public EntityBean cacheEmbeddedBeanLoad(CachedBeanData data) { + return cacheHelp.embeddedBeanLoad(data); } /** - * Put a bean into the bean cache. + * Load the embedded bean as the root type. */ - public void cacheBeanPutData(EntityBean bean) { + EntityBean cacheEmbeddedBeanLoadDirect(CachedBeanData data) { + return cacheHelp.embeddedBeanLoadDirect(data); + } + + /** + * Load the entity bean as the correct bean type. + */ + EntityBean cacheBeanLoadDirect(Object id, Boolean readOnly, CachedBeanData data) { + return cacheHelp.loadBeanDirect(id, readOnly, data); + } + + /** + * Put the bean into the cache. + */ + public void cacheBeanPut(T bean) { + cacheBeanPut((EntityBean) bean); + } + + /** + * Put a bean into the bean cache (taking into account inheritance). + */ + public void cacheBeanPut(EntityBean bean) { cacheHelp.beanCachePut(bean); } + /** + * Put a bean into the cache as the correct type. + */ + void cacheBeanPutDirect(EntityBean bean) { + cacheHelp.beanCachePutDirect(bean); + } + /** * Return a bean from the bean cache (or null). */ @@ -1157,7 +1198,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Remove a bean from the cache given its Id. */ - public void cacheBeanRemove(Object id) { + public void cacheHandleDeleteById(Object id) { cacheHelp.beanCacheRemove(id); } @@ -1184,6 +1225,10 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return cacheHelp.naturalKeyLookup(query, t); } + public void cacheNaturalKeyPut(Object id, Object newKey) { + cacheHelp.cacheNaturalKeyPut(id, newKey); + } + /** * Invalidate parts of cache due to SqlUpdate or external modification etc. */ @@ -1192,21 +1237,38 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { } /** - * Remove a bean from the cache given its Id. + * Handle a delete by id request adding an cache change into the changeSet. */ - public void cacheHandleDelete(Object id, PersistRequestBean deleteRequest) { - cacheHelp.handleDelete(id, deleteRequest); - } - - public void cacheHandleInsert(PersistRequestBean insertRequest) { - cacheHelp.handleInsert(insertRequest); + public void cacheHandleDeleteById(Object id, CacheChangeSet changeSet) { + cacheHelp.handleDelete(id, changeSet); } /** - * Update the cached bean data. + * Remove a bean from the cache given its Id. */ - public void cacheHandleUpdate(Object id, PersistRequestBean updateRequest) { - cacheHelp.handleUpdate(id, updateRequest); + public void cacheHandleDelete(Object id, PersistRequestBean deleteRequest, CacheChangeSet changeSet) { + cacheHelp.handleDelete(id, deleteRequest, changeSet); + } + + /** + * Add the insert changes to the changeSet. + */ + public void cacheHandleInsert(PersistRequestBean insertRequest, CacheChangeSet changeSet) { + cacheHelp.handleInsert(insertRequest, changeSet); + } + + /** + * Add the update to the changeSet. + */ + public void cacheHandleUpdate(Object id, PersistRequestBean updateRequest, CacheChangeSet changeSet) { + cacheHelp.handleUpdate(id, updateRequest, changeSet); + } + + /** + * Apply the update to the cache. + */ + public void cacheBeanUpdate(Object id, Map changes, boolean updateNaturalKey, long version) { + cacheHelp.cacheBeanUpdate(id, changes, updateNaturalKey, version); } /** @@ -2100,6 +2162,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return inheritInfo.getDiscriminatorColumn(); } + /** + * Return the discriminator value for this bean type (or null when there is no inheritance). + */ + public String getDiscValue() { + return inheritInfo == null ? null : inheritInfo.getDiscriminatorStringValue(); + } + @Override @SuppressWarnings("unchecked") public T createBeanUsingDisc(Object discValue) { @@ -2534,6 +2603,25 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { return versionPropertyIndex > -1 && ebi.isLoadedProperty(versionPropertyIndex); } + /** + * Set the version value returning it in primitive long form. + */ + public long setVersion(EntityBean entityBean, Object versionValue) { + versionProperty.setValueIntercept(entityBean, versionValue); + return versionProperty.scalarType.asVersion(versionValue); + } + + /** + * Return the version value in primitive long form (if exists and set). + */ + public long getVersion(EntityBean entityBean) { + if (versionProperty == null) { + return 0; + } + Object value = versionProperty.getValue(entityBean); + return value == null ? 0 : versionProperty.scalarType.asVersion(value); + } + /** * Check for mutable scalar types and mark as dirty if necessary. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java index e386e8fa2..9bc143455 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java @@ -11,10 +11,10 @@ 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.CacheChangeSet; import com.avaje.ebeaninternal.server.cache.CachedBeanData; import com.avaje.ebeaninternal.server.cache.CachedBeanDataFromBean; import com.avaje.ebeaninternal.server.cache.CachedBeanDataToBean; -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; @@ -25,20 +25,21 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * Helper for BeanDescriptor that manages the bean, query and collection caches. * * @param The entity bean type */ -public final class BeanDescriptorCacheHelp { +final class BeanDescriptorCacheHelp { - 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 static final Logger queryLog = LoggerFactory.getLogger("org.avaje.ebean.cache.QUERY"); + private static final Logger beanLog = LoggerFactory.getLogger("org.avaje.ebean.cache.BEAN"); + private static final Logger manyLog = LoggerFactory.getLogger("org.avaje.ebean.cache.COLL"); + private static final Logger natLog = LoggerFactory.getLogger("org.avaje.ebean.cache.NATKEY"); private final BeanDescriptor desc; @@ -51,26 +52,28 @@ public final class BeanDescriptorCacheHelp { */ private final boolean cacheSharableBeans; - private final Class beanType; + private final Class beanType; private final String cacheName; private final BeanPropertyAssocOne[] propertiesOneImported; + private final String naturalKeyProperty; private ServerCache beanCache; private ServerCache naturalKeyCache; private volatile ServerCache queryCache; - public BeanDescriptorCacheHelp(BeanDescriptor desc, ServerCacheManager cacheManager, CacheOptions cacheOptions, + BeanDescriptorCacheHelp(BeanDescriptor desc, ServerCacheManager cacheManager, CacheOptions cacheOptions, boolean cacheSharableBeans, BeanPropertyAssocOne[] propertiesOneImported) { this.desc = desc; - this.beanType = desc.getBeanType(); + this.beanType = desc.rootBeanType; this.cacheName = beanType.getSimpleName(); this.cacheManager = cacheManager; this.cacheOptions = cacheOptions; this.cacheSharableBeans = cacheSharableBeans; this.propertiesOneImported = propertiesOneImported; + this.naturalKeyProperty = cacheOptions.getNaturalKey(); } /** @@ -117,21 +120,21 @@ public final class BeanDescriptorCacheHelp { /** * Return true if there is currently query caching for this type of bean. */ - public boolean isQueryCaching() { + private boolean isQueryCaching() { return queryCache != null; } /** * Return true if there is currently bean caching for this type of bean. */ - public boolean isBeanCaching() { + boolean isBeanCaching() { return beanCache != null; } /** * Return true if the persist request needs to notify the cache. */ - public boolean isCacheNotify() { + boolean isCacheNotify() { if (isBeanCaching() || isQueryCaching()) { return true; @@ -144,7 +147,7 @@ public final class BeanDescriptorCacheHelp { return false; } - public CacheOptions getCacheOptions() { + CacheOptions getCacheOptions() { return cacheOptions; } @@ -152,7 +155,7 @@ public final class BeanDescriptorCacheHelp { * Initialise the query cache if required * (as some node in the cluster already has it). */ - public void queryCacheInit() { + void queryCacheInit() { if (queryCache == null) { queryLog.debug(" init {}", cacheName); queryCache = cacheManager.getQueryCache(beanType); @@ -162,7 +165,7 @@ public final class BeanDescriptorCacheHelp { /** * Clear the query cache. */ - public void queryCacheClear() { + void queryCacheClear() { if (queryCache != null) { if (queryLog.isDebugEnabled()) { queryLog.debug(" CLEAR {}", cacheName); @@ -171,12 +174,20 @@ public final class BeanDescriptorCacheHelp { } } + /** + * Add query cache clear to the changeSet. + */ + void queryCacheClear(CacheChangeSet changeSet) { + if (queryCache != null) { + changeSet.addClearQuery(desc); + } + } /** * Get a query result from the query cache. */ @SuppressWarnings("unchecked") - public BeanCollection queryCacheGet(Object id) { + BeanCollection queryCacheGet(Object id) { if (queryCache == null) { return null; } else { @@ -195,7 +206,7 @@ public final class BeanDescriptorCacheHelp { /** * Put a query result into the query cache. */ - public void queryCachePut(Object id, BeanCollection query) { + void queryCachePut(Object id, BeanCollection query) { if (queryCache == null) { queryCache = cacheManager.getQueryCache(beanType); } @@ -206,7 +217,7 @@ public final class BeanDescriptorCacheHelp { } - public void manyPropRemove(Object parentId, String propertyName) { + void manyPropRemove(String propertyName, Object parentId) { ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); if (manyLog.isDebugEnabled()) { manyLog.debug(" REMOVE {}({}).{}", cacheName, parentId, propertyName); @@ -214,7 +225,7 @@ public final class BeanDescriptorCacheHelp { collectionIdsCache.remove(parentId); } - public void manyPropClear(String propertyName) { + void manyPropClear(String propertyName) { ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); if (manyLog.isDebugEnabled()) { manyLog.debug(" CLEAR {}(*).{} ", cacheName, propertyName); @@ -225,7 +236,7 @@ public final class BeanDescriptorCacheHelp { /** * Return the CachedManyIds for a given bean many property. Returns null if not in the cache. */ - public CachedManyIds manyPropGet(Object parentId, String propertyName) { + private CachedManyIds manyPropGet(Object parentId, String propertyName) { ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, propertyName); CachedManyIds entry = (CachedManyIds) collectionIdsCache.get(parentId); if (entry == null) { @@ -241,7 +252,7 @@ public final class BeanDescriptorCacheHelp { /** * Try to load the bean collection from cache return true if successful. */ - public boolean manyPropLoad(BeanPropertyAssocMany many, BeanCollection bc, Object parentId, Boolean readOnly) { + boolean manyPropLoad(BeanPropertyAssocMany many, BeanCollection bc, Object parentId, Boolean readOnly) { CachedManyIds entry = manyPropGet(parentId, many.getName()); if (entry == null) { @@ -272,30 +283,37 @@ public final class BeanDescriptorCacheHelp { /** * Put the beanCollection into the cache. */ - public void manyPropPut(BeanPropertyAssocMany many, Object details, Object parentId) { - - BeanDescriptor targetDescriptor = many.getTargetDescriptor(); - ArrayList idList = new ArrayList(); + void manyPropPut(BeanPropertyAssocMany many, Object details, Object parentId) { - // get the underlying collection of beans (in the List, Set or Map) - Collection actualDetails = BeanCollectionUtil.getActualEntries(details); - - for (Object bean : actualDetails) { - // Collect the id values - idList.add(targetDescriptor.getId((EntityBean) bean)); - } - - CachedManyIds entry = new CachedManyIds(idList); - ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, many.getName()); + CachedManyIds entry = createManyIds(many, details); + cachePutManyIds(parentId, many.getName(), entry); + } + + void cachePutManyIds(Object parentId, String manyName, CachedManyIds entry) { + + ServerCache collectionIdsCache = cacheManager.getCollectionIdsCache(beanType, manyName); if (manyLog.isDebugEnabled()) { - manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, many.getName(), entry); + manyLog.debug(" PUT {}({}).{} - ids:{}", cacheName, parentId, manyName, entry); } collectionIdsCache.put(parentId, entry); } + private CachedManyIds createManyIds(BeanPropertyAssocMany many, Object details) { + BeanDescriptor targetDescriptor = many.getTargetDescriptor(); - public T naturalKeyLookup(SpiQuery query, SpiTransaction t) { + List idList = new ArrayList(); + Collection actualDetails = BeanCollectionUtil.getActualEntries(details); + for (Object bean : actualDetails) { + idList.add(targetDescriptor.getId((EntityBean) bean)); + } + return new CachedManyIds(idList); + } + + /** + * Find the bean using the natural key lookup if available. + */ + T naturalKeyLookup(SpiQuery query, SpiTransaction t) { if (!isNaturalKeyCaching(query.isUseBeanCache())) { // no natural key caching for this query @@ -345,8 +363,6 @@ public final class BeanDescriptorCacheHelp { return propName != null && propName.equals(cacheOptions.getNaturalKey()); } - - /** * For a bean built from the cache this sets up its persistence context for future lazy loading etc. */ @@ -377,7 +393,7 @@ public final class BeanDescriptorCacheHelp { /** * Clear the bean cache. */ - public void beanCacheClear() { + private void beanCacheClear() { if (beanCache != null) { if (beanLog.isDebugEnabled()) { beanLog.debug(" CLEAR {}", cacheName); @@ -386,29 +402,37 @@ public final class BeanDescriptorCacheHelp { } } - public CachedBeanData beanExtractData(EntityBean bean) { - return CachedBeanDataFromBean.extract(desc, bean); + CachedBeanData beanExtractData(BeanDescriptor targetDesc, EntityBean bean) { + return CachedBeanDataFromBean.extract(targetDesc, bean); } - public void beanLoadData(EntityBean bean, CachedBeanData data) { - CachedBeanDataToBean.load(desc, bean, data); - } - /** * Put a bean into the bean cache. */ - public void beanCachePut(EntityBean bean) { + void beanCachePut(EntityBean bean) { - CachedBeanData beanData = beanExtractData(bean); + if (desc.inheritInfo != null) { + desc.inheritInfo.readType(bean.getClass()).getBeanDescriptor().cacheBeanPutDirect(bean); + } else { + beanCachePutDirect(bean); + } + } + + /** + * Put the bean into the bean cache. + */ + void beanCachePutDirect(EntityBean bean) { + + CachedBeanData beanData = beanExtractData(desc, bean); Object id = desc.getId(bean); if (beanLog.isDebugEnabled()) { - beanLog.debug(" PUT {}({})", cacheName, id); + beanLog.debug(" PUT {}({}) data:{}", cacheName, id, beanData); } getBeanCache().put(id, beanData); - - if (beanData.isNaturalKeyUpdate() && naturalKeyCache != null) { - Object naturalKey = beanData.getNaturalKey(); + + if (naturalKeyProperty != null) { + Object naturalKey = beanData.getData(naturalKeyProperty); if (naturalKey != null) { if (natLog.isDebugEnabled()) { natLog.debug(" PUT {}({}, {})", cacheName, naturalKey, id); @@ -418,11 +442,11 @@ public final class BeanDescriptorCacheHelp { } } - public CachedBeanData beanCacheGetData(Object id) { + CachedBeanData beanCacheGetData(Object id) { return (CachedBeanData) getBeanCache().get(id); } - public T beanCacheGet(SpiQuery query, PersistenceContext context) { + T beanCacheGet(SpiQuery query, PersistenceContext context) { Object id = desc.convertId(query.getId()); T bean = beanCacheGetInternal(id, query.isReadOnly()); if (bean != null) { @@ -457,30 +481,80 @@ public final class BeanDescriptorCacheHelp { } } + return (T)loadBean(id, readOnly, data); + } + + /** + * Load the entity bean taking into account inheritance. + */ + private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data) { + + String discValue = data.getDiscValue(); + if (discValue == null) { + return loadBeanDirect(id, readOnly, data); + } else { + return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data); + } + } + + /** + * Return the root BeanDescriptor for inheritance. + */ + private BeanDescriptor rootDescriptor(String discValue) { + InheritInfo inheritInfo = desc.inheritInfo.readType(discValue); + return inheritInfo.getBeanDescriptor(); + } + + /** + * Load the entity bean from cache data given this is the root bean type. + */ + EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data) { + EntityBean bean = desc.createEntityBean(); desc.convertSetId(id, bean); + CachedBeanDataToBean.load(desc, bean, data); + EntityBeanIntercept ebi = bean._ebean_getIntercept(); ebi.setBeanLoader(desc.getEbeanServer()); - if (Boolean.TRUE.equals(readOnly)) { ebi.setReadOnly(true); } - beanLoadData(bean, data); - if (beanLog.isTraceEnabled()) { beanLog.trace(" GET {}({}) - hit", cacheName, id); } if (desc.isReadAuditing()) { desc.readAuditBean("l2", "", bean); } - return (T) bean; + return bean; + } + + /** + * Load the embedded bean checking for inheritance. + */ + EntityBean embeddedBeanLoad(CachedBeanData data) { + + String discValue = data.getDiscValue(); + if (discValue == null) { + return embeddedBeanLoadDirect(data); + } else { + return rootDescriptor(discValue).cacheEmbeddedBeanLoadDirect(data); + } + } + + /** + * Load the embedded bean given this is the bean type. + */ + EntityBean embeddedBeanLoadDirect(CachedBeanData data) { + EntityBean bean = desc.createEntityBean(); + CachedBeanDataToBean.load(desc, bean, data); + return bean; } /** * Remove a bean from the cache given its Id. */ - public void beanCacheRemove(Object id) { + void beanCacheRemove(Object id) { if (beanCache != null) { if (beanLog.isDebugEnabled()) { beanLog.debug(" REMOVE {}({})", cacheName, id); @@ -495,7 +569,7 @@ public final class BeanDescriptorCacheHelp { /** * Returns true if it managed to populate/load the bean from the cache. */ - public boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) { + boolean beanCacheLoad(EntityBean bean, EntityBeanIntercept ebi, Object id) { CachedBeanData cacheData = (CachedBeanData) getBeanCache().get(id); if (cacheData == null) { @@ -505,7 +579,7 @@ public final class BeanDescriptorCacheHelp { return false; } int lazyLoadProperty = ebi.getLazyLoadPropertyIndex(); - if (lazyLoadProperty > -1 && !cacheData.isLoaded(lazyLoadProperty)) { + if (lazyLoadProperty > -1 && !cacheData.isLoaded(ebi.getLazyLoadProperty())) { if (beanLog.isTraceEnabled()) { beanLog.trace(" LOAD {}({}) - cache miss on property", cacheName, id); } @@ -518,110 +592,81 @@ public final class BeanDescriptorCacheHelp { } return true; } - + /** - * Remove a bean from the cache given its Id. + * Add appropriate cache changes to support delete. */ - public void handleDelete(Object id, PersistRequestBean deleteRequest) { - if (queryCache != null) { - if (queryLog.isDebugEnabled()) { - queryLog.debug(" CLEAR {}(*) - delete trigger", cacheName); - } - queryCache.clear(); - } + void handleDelete(Object id, CacheChangeSet changeSet) { if (beanCache != null) { - if (beanLog.isDebugEnabled()) { - beanLog.debug(" REMOVE {}({})", cacheName, id); - } - beanCache.remove(id); - } - for (int i = 0; i < propertiesOneImported.length; i++) { - BeanPropertyAssocMany many = propertiesOneImported[i].getRelationshipProperty(); - if (many != null) { - propertiesOneImported[i].cacheDelete(true, deleteRequest.getEntityBean()); - } + changeSet.addBeanRemove(desc, id); } + cacheDeleteImported(true, null, changeSet); } - public void handleInsert(PersistRequestBean insertRequest) { - if (queryCache != null) { - if (queryLog.isDebugEnabled()) { - queryLog.debug(" CLEAR {}(*) - insert trigger", cacheName); - } - queryCache.clear(); + /** + * Add appropriate cache changes to support delete. + */ + void handleDelete(Object id, PersistRequestBean deleteRequest, CacheChangeSet changeSet) { + queryCacheClear(changeSet); + if (beanCache != null) { + changeSet.addBeanRemove(desc, id); } + cacheDeleteImported(true, deleteRequest.getEntityBean(), changeSet); + } + + /** + * Add appropriate cache changes to support insert. + */ + void handleInsert(PersistRequestBean insertRequest, CacheChangeSet changeSet) { + queryCacheClear(changeSet); + cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet); + } + + private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) { for (int i = 0; i < propertiesOneImported.length; i++) { - propertiesOneImported[i].cacheDelete(false, insertRequest.getEntityBean()); + propertiesOneImported[i].cacheDelete(clear, entityBean, changeSet); } } /** - * Update the cached bean data. + * Add appropriate changes to support update. */ - public void handleUpdate(Object id, PersistRequestBean updateRequest) { + void handleUpdate(Object id, PersistRequestBean updateRequest, CacheChangeSet changeSet) { - if (queryCache != null) { - if (queryLog.isDebugEnabled()) { - queryLog.debug(" CLEAR {}(*) - update trigger", cacheName); - } - queryCache.clear(); - } + queryCacheClear(changeSet); List> 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()); + BeanPropertyAssocMany many = manyCollections.get(i); + Object details = many.getValue(updateRequest.getEntityBean()); + CachedManyIds entry = createManyIds(many, details); + changeSet.addManyPut(desc, many.getName(), id, entry); } } // 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) { - CachedBeanData newData = CachedBeanDataUpdate.update(desc, existingData, updateRequest.getEntityBean()); - 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 - if (beanLog.isDebugEnabled()) { - beanLog.debug(" UPDATE {}({})", cacheName, id); - } - cache.put(id, newData); - } + boolean updateNaturalKey = false; - 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); + Map changes = new LinkedHashMap(); + EntityBean bean = updateRequest.getEntityBean(); + boolean[] dirtyProperties = updateRequest.getDirtyProperties(); + for (int i = 0; i < dirtyProperties.length; i++) { + if (dirtyProperties[i]) { + BeanProperty property = desc.propertiesIndex[i]; + Object val = property.getCacheDataValue(bean); + changes.put(property.getName(), val); + if (property.isNaturalKey()) { + updateNaturalKey = true; + changeSet.addNaturalKeyPut(desc, id, val); } } } + + changeSet.addBeanUpdate(desc, id, changes, updateNaturalKey, updateRequest.getVersion()); } - - 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) { @@ -631,7 +676,7 @@ public final class BeanDescriptorCacheHelp { /** * Invalidate parts of cache due to SqlUpdate or external modification etc. */ - public void handleBulkUpdate(TableIUD tableIUD) { + void handleBulkUpdate(TableIUD tableIUD) { // inserts don't invalidate the bean cache if (tableIUD.isUpdateOrDelete()) { beanCacheClear(); @@ -639,4 +684,55 @@ public final class BeanDescriptorCacheHelp { // any change invalidates the query cache queryCacheClear(); } + + void cacheNaturalKeyPut(Object id, Object newKey) { + if (newKey != null) { + naturalKeyCache.put(newKey, id); + } + } + + /** + * Apply changes to the bean cache entry. + */ + void cacheBeanUpdate(Object id, Map changes, boolean updateNaturalKey, long version) { + + ServerCache cache = getBeanCache(); + CachedBeanData existingData = (CachedBeanData) cache.get(id); + if (existingData != null) { + if (isCachedDataTooOld(existingData)) { + if (beanLog.isDebugEnabled()) { + beanLog.debug(" REMOVE {}({}) - entry too old", cacheName, id); + } + cache.remove(id); + } else { + long currentVersion = existingData.getVersion(); + if (version > 0 && version < currentVersion) { + if (beanLog.isDebugEnabled()) { + beanLog.debug(" REMOVE {}({}) - version conflict old:{} new:{}", cacheName, id, currentVersion, version); + } + cache.remove(id); + } else { + if (version == 0) { + version = currentVersion; + } + CachedBeanData newData = existingData.update(changes, version); + if (beanLog.isDebugEnabled()) { + beanLog.debug(" UPDATE {}({}) changes:{}", cacheName, id, changes); + } + cache.put(id, newData); + } + } + + if (updateNaturalKey) { + Object oldKey = existingData.getData(naturalKeyProperty); + if (oldKey != null) { + if (natLog.isDebugEnabled()) { + natLog.debug(".. update {} REMOVE({}) - old key for ({})", cacheName, oldKey, id); + } + naturalKeyCache.remove(oldKey); + } + } + } + } + } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 7c0f705e0..a310f43a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -6,6 +6,7 @@ import com.avaje.ebean.SqlUpdate; import com.avaje.ebean.Transaction; import com.avaje.ebean.ValuePair; import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeaninternal.server.cache.CachedBeanData; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.deploy.id.ImportedId; @@ -142,19 +143,20 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } } - void cacheDelete(boolean clearOnNull, EntityBean bean) { + void cacheDelete(boolean clear, EntityBean bean, CacheChangeSet changeSet) { + if (targetDescriptor.isBeanCaching() && relationshipProperty != null) { - Object assocBean = getValue(bean); - if (assocBean != null) { - Object parentId = targetDescriptor.getId((EntityBean) assocBean); - if (parentId != null) { - targetDescriptor.cacheManyPropRemove(parentId, relationshipProperty.getName()); - return; + if (clear) { + changeSet.addManyClear(targetDescriptor, relationshipProperty.getName()); + } else { + Object assocBean = getValue(bean); + if (assocBean != null) { + Object parentId = targetDescriptor.getId((EntityBean) assocBean); + if (parentId != null) { + changeSet.addManyRemove(targetDescriptor, relationshipProperty.getName(), parentId); + } } } - if (clearOnNull) { - targetDescriptor.cacheManyPropClear(relationshipProperty.getName()); - } } } @@ -389,7 +391,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { return null; } if (embedded) { - return targetDescriptor.cacheBeanExtractData((EntityBean) ap); + return targetDescriptor.cacheEmbeddedBeanExtract((EntityBean) ap); } else { return targetDescriptor.getId((EntityBean) ap); @@ -400,8 +402,7 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { public void setCacheDataValue(EntityBean bean, Object cacheData) { if (cacheData != null) { if (embedded) { - EntityBean embeddedBean = targetDescriptor.createEntityBean(); - targetDescriptor.cacheBeanLoadData(embeddedBean, (CachedBeanData) cacheData); + EntityBean embeddedBean = targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData); setValue(bean, embeddedBean); } else { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java index 2f70f8516..f49c17e4a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -101,7 +101,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine { BeanDescriptor descriptor = request.getBeanDescriptor(); Collection c = result.getActualDetails(); for (T bean : c) { - descriptor.cacheBeanPutData((EntityBean) bean); + descriptor.cacheBeanPut((EntityBean) bean); } } @@ -130,7 +130,7 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine { } if (result != null && request.isUseBeanCache()) { - request.getBeanDescriptor().cacheBeanPutData((EntityBean) result); + request.getBeanDescriptor().cacheBeanPut((EntityBean) result); } return result; diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java index 0f75fc792..88aa79c22 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/BeanPersistIds.java @@ -235,7 +235,7 @@ public class BeanPersistIds { if (updateIds != null) { for (int i = 0; i < updateIds.size(); i++) { Object id = updateIds.get(i); - beanDescriptor.cacheBeanRemove(id); + beanDescriptor.cacheHandleDeleteById(id); if (listener != null) { listener.remoteUpdate(id); } @@ -244,7 +244,7 @@ public class BeanPersistIds { if (deleteIds != null) { for (int i = 0; i < deleteIds.size(); i++) { Object id = deleteIds.get(i); - beanDescriptor.cacheBeanRemove(id); + beanDescriptor.cacheHandleDeleteById(id); if (listener != null) { listener.remoteDelete(id); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java index 80dcbb959..55b47ff76 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/DeleteByIdMap.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.transaction; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeaninternal.server.core.PersistRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; @@ -24,18 +25,17 @@ public final class DeleteByIdMap { return beanMap.toString(); } - public void notifyCache() { + public void notifyCache(CacheChangeSet changeSet) { for (BeanPersistIds deleteIds : beanMap.values()) { BeanDescriptor d = deleteIds.getBeanDescriptor(); List idValues = deleteIds.getDeleteIds(); if (idValues != null) { - d.queryCacheClear(); + d.queryCacheClear(changeSet); for (int i = 0; i < idValues.size(); i++) { - d.cacheBeanRemove(idValues.get(i)); + d.cacheHandleDeleteById(idValues.get(i), changeSet); } } } - } public boolean isEmpty() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java index 7eb19291d..733effaa0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java @@ -5,6 +5,7 @@ import com.avaje.ebeaninternal.api.SpiTransaction; import com.avaje.ebeaninternal.api.TransactionEvent; import com.avaje.ebeaninternal.api.TransactionEventTable; import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; +import com.avaje.ebeaninternal.server.cache.CacheChangeSet; import com.avaje.ebeanservice.docstore.api.DocStoreUpdates; import com.avaje.ebeaninternal.server.cluster.ClusterManager; import com.avaje.ebeaninternal.server.core.PersistRequestBean; @@ -44,6 +45,8 @@ public final class PostCommitProcessing { private final int txnDocStoreBatchSize; + private CacheChangeSet cacheChanges; + /** * Create for an external modification. */ @@ -83,7 +86,7 @@ public final class PostCommitProcessing { */ void notifyLocalCache() { processTableEvents(event.getEventTables()); - event.notifyCache(); + cacheChanges = event.buildCacheChanges(); } /** @@ -145,6 +148,9 @@ public final class PostCommitProcessing { Runnable backgroundNotify() { return new Runnable() { public void run() { + if (cacheChanges != null) { + cacheChanges.apply(); + } localPersistListenersNotify(); notifyCluster(); processDocStoreUpdates(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java index 1e1f65ea2..da6d657b0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarType.java @@ -181,6 +181,11 @@ public interface ScalarType extends StringParser, StringFormatter, ScalarData */ boolean isDateTimeCapable(); + /** + * Convert the value into a long version value. + */ + long asVersion(T value); + /** * Convert the systemTimeMillis into the appropriate java object. *

diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java index 648996605..b31731aef 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBase.java @@ -17,6 +17,11 @@ public abstract class ScalarTypeBase implements ScalarType { this.jdbcType = jdbcType; } + @Override + public long asVersion(T value) { + throw new RuntimeException("not supported"); + } + /** * Default implementation of mutable false. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java index f15c7eabd..fdb3bcbc2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBaseDateTime.java @@ -28,6 +28,11 @@ public abstract class ScalarTypeBaseDateTime extends ScalarTypeBase { this.mode = mode; } + @Override + public long asVersion(T value) { + return convertToMillis(value); + } + /** * Convert the value to a Timestamp. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java index 257fd9266..7cca1c9da 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeBytesEncrypted.java @@ -24,6 +24,11 @@ public class ScalarTypeBytesEncrypted implements ScalarType { this.dataEncryptSupport = dataEncryptSupport; } + @Override + public long asVersion(byte[] value) { + throw new RuntimeException("not supported"); + } + @Override public boolean isMutable() { return false; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java index 9cf74f6ba..997919141 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEncryptedWrapper.java @@ -23,6 +23,11 @@ public class ScalarTypeEncryptedWrapper implements ScalarType { this.dataEncryptSupport = dataEncryptSupport; } + @Override + public long asVersion(T value) { + throw new RuntimeException("not supported"); + } + @Override public boolean isMutable() { return wrapped.isMutable(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java index 0835881a5..41db357ca 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeEnumWithMapping.java @@ -24,6 +24,11 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i this.length = length; } + @Override + public long asVersion(Object value) { + throw new RuntimeException("not supported"); + } + /** * Return the IN values for DB constraint construction. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java index 52787032f..063406a81 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeInteger.java @@ -54,6 +54,11 @@ public class ScalarTypeInteger extends ScalarTypeBase { } } + @Override + public long asVersion(Integer value) { + return value.longValue(); + } + @Override public Object toJdbcType(Object value) { return BasicTypeConverter.toInteger(value); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java index 9af0a8fb3..878326f54 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeLong.java @@ -55,6 +55,11 @@ public class ScalarTypeLong extends ScalarTypeBase { return Long.valueOf(value); } + @Override + public long asVersion(Long value) { + return value; + } + @Override public Long convertFromMillis(long systemTimeMillis) { return systemTimeMillis; diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java index a75ce7e17..aaa1a957e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeWrapper.java @@ -41,6 +41,12 @@ public class ScalarTypeWrapper implements ScalarType { return "ScalarTypeWrapper " + wrapperType + " to " + scalarType.getType(); } + @Override + public long asVersion(B value) { + S unwrapValue = converter.unwrapValue(value); + return scalarType.asVersion(unwrapValue); + } + @Override public boolean isMutable() { return scalarType.isMutable(); diff --git a/src/test/java/com/avaje/ebean/BaseTestCase.java b/src/test/java/com/avaje/ebean/BaseTestCase.java index cbc438435..1a1f3875b 100644 --- a/src/test/java/com/avaje/ebean/BaseTestCase.java +++ b/src/test/java/com/avaje/ebean/BaseTestCase.java @@ -31,6 +31,17 @@ public class BaseTestCase { return spi.getDatabasePlatform().getName().equals("h2"); } + /** + * Wait for the L2 cache to propagate changes post-commit. + */ + protected void awaitL2Cache() { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + protected BeanDescriptor getBeanDescriptor(Class cls) { return spiEbeanServer().getBeanDescriptor(cls); } diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/CacheBeanDataTest.java b/src/test/java/com/avaje/ebeaninternal/server/cache/CacheBeanDataTest.java new file mode 100644 index 000000000..86af25558 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/cache/CacheBeanDataTest.java @@ -0,0 +1,119 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.tests.model.basic.Address; +import com.avaje.tests.model.basic.Country; +import com.avaje.tests.model.basic.Customer; +import com.avaje.tests.model.basic.Customer.Status; +import com.avaje.tests.model.embedded.EAddress; +import com.avaje.tests.model.embedded.EPerson; +import org.junit.Test; + +import java.sql.Timestamp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class CacheBeanDataTest extends BaseTestCase { + + @Test + public void extract_load_on_customer() { + + SpiEbeanServer server = spiEbeanServer(); + BeanDescriptor desc = server.getBeanDescriptor(Customer.class); + + Customer c = new Customer(); + c.setId(98989); + c.setName("Rob"); + c.setCretime(new Timestamp(System.currentTimeMillis())); + c.setUpdtime(new Timestamp(System.currentTimeMillis())); + c.setStatus(Status.ACTIVE); + c.setSmallnote("somenote"); + + Address billingAddress = new Address(); + billingAddress.setId((short) 12); + billingAddress.setCity("Auckland"); + billingAddress.setCountry(server.getReference(Country.class, "NZ")); + billingAddress.setLine1("92 Someplace Else"); + c.setBillingAddress(billingAddress); + + ((EntityBean) c)._ebean_getIntercept().setNewBeanForUpdate(); + + CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, (EntityBean) c); + + + assertNotNull(cacheData); + + Customer newCustomer = new Customer(); + newCustomer.setId(c.getId()); + CachedBeanDataToBean.load(desc, (EntityBean) newCustomer, cacheData); + + assertEquals(c.getId(), newCustomer.getId()); + assertEquals(c.getName(), newCustomer.getName()); + assertEquals(c.getStatus(), newCustomer.getStatus()); + assertEquals(c.getSmallnote(), newCustomer.getSmallnote()); + assertEquals(c.getCretime(), newCustomer.getCretime()); + assertEquals(c.getUpdtime(), newCustomer.getUpdtime()); + assertEquals(c.getBillingAddress().getId(), newCustomer.getBillingAddress().getId()); + + assertNotNull(newCustomer.getId()); + assertNotNull(newCustomer.getName()); + assertNotNull(newCustomer.getStatus()); + assertNotNull(newCustomer.getSmallnote()); + assertNotNull(newCustomer.getCretime()); + assertNotNull(newCustomer.getUpdtime()); + assertNotNull(newCustomer.getBillingAddress()); + assertNotNull(newCustomer.getBillingAddress().getId()); + + } + + + @Test + public void extract_load_withEmbeddedBean() { + + SpiEbeanServer server = spiEbeanServer(); + BeanDescriptor desc = server.getBeanDescriptor(EPerson.class); + BeanPropertyAssocOne addressBeanProperty = (BeanPropertyAssocOne) desc.getBeanProperty("address"); + + EAddress address = new EAddress(); + address.setStreet("92 Someplace Else"); + address.setSuburb("Sandringham"); + address.setCity("Auckland"); + + EPerson person = new EPerson(); + person.setId(98989L); + person.setName("Rob"); + person.setAddress(address); + + CachedBeanData addressCacheData = (CachedBeanData) addressBeanProperty.getCacheDataValue((EntityBean) person); + + EPerson newPersonCheck = new EPerson(); + newPersonCheck.setId(98989L); + addressBeanProperty.setCacheDataValue((EntityBean) newPersonCheck, addressCacheData); + + EAddress newAddress = newPersonCheck.getAddress(); + assertEquals(address.getStreet(), newAddress.getStreet()); + assertEquals(address.getCity(), newAddress.getCity()); + assertEquals(address.getSuburb(), newAddress.getSuburb()); + + + CachedBeanData cacheData = desc.cacheEmbeddedBeanExtract((EntityBean) person); + + assertNotNull(cacheData); + + EPerson newPerson = (EPerson)desc.cacheEmbeddedBeanLoad(cacheData); + + assertNotNull(newPerson.getId()); + assertNotNull(newPerson.getName()); + assertNotNull(newPerson.getAddress()); + + assertEquals(person.getId(), newPerson.getId()); + assertEquals(person.getName(), newPerson.getName()); + assertEquals(person.getAddress().getStreet(), newPerson.getAddress().getStreet()); + assertEquals(person.getAddress().getCity(), newPerson.getAddress().getCity()); + } +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBeanTest.java b/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBeanTest.java new file mode 100644 index 000000000..7ef133f39 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataFromBeanTest.java @@ -0,0 +1,60 @@ +package com.avaje.ebeaninternal.server.cache; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.tests.model.basic.Address; +import com.avaje.tests.model.basic.Car; +import com.avaje.tests.model.basic.Customer; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CachedBeanDataFromBeanTest extends BaseTestCase { + + SpiEbeanServer server = spiEbeanServer(); + + @Test + public void extract() throws Exception { + + BeanDescriptor desc = server.getBeanDescriptor(Customer.class); + + Customer customer = new Customer(); + customer.setId(42); + customer.setName("Rob"); + + Address billingAddress = new Address(); + billingAddress.setId(Short.valueOf("12")); + billingAddress.setCity("SomePlace"); + + customer.setBillingAddress(billingAddress); + + CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, (EntityBean) customer); + + assertEquals(cacheData.getData("id"), Integer.valueOf(42)); + assertEquals(cacheData.getData("name"), "Rob"); + assertEquals(cacheData.getData("billingAddress"), Short.valueOf("12")); + } + + + @Test + public void inheritance() { + + Car car = new Car(); + car.setId(42); + car.setDriver("Jimmy"); + car.setNotes("some notes"); + + BeanDescriptor carDesc = server.getBeanDescriptor(Car.class); + CachedBeanData cacheData = CachedBeanDataFromBean.extract(carDesc, (EntityBean) car); + + Car newCar = new Car(); + EntityBean entityBean = (EntityBean)newCar; + CachedBeanDataToBean.load(carDesc, entityBean, cacheData); + + assertEquals(newCar.getId(), car.getId()); + assertEquals(newCar.getDriver(), car.getDriver()); + assertEquals(newCar.getNotes(), car.getNotes()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java b/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java deleted file mode 100644 index c23152f5c..000000000 --- a/src/test/java/com/avaje/ebeaninternal/server/cache/TestCacheBeanData.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.avaje.ebeaninternal.server.cache; - -import java.sql.Timestamp; - -import org.junit.Assert; -import org.junit.Test; - -import com.avaje.ebean.BaseTestCase; -import com.avaje.ebean.Ebean; -import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebeaninternal.api.SpiEbeanServer; -import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; -import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; -import com.avaje.tests.model.basic.Address; -import com.avaje.tests.model.basic.Country; -import com.avaje.tests.model.basic.Customer; -import com.avaje.tests.model.basic.Customer.Status; -import com.avaje.tests.model.embedded.EAddress; -import com.avaje.tests.model.embedded.EPerson; - -public class TestCacheBeanData extends BaseTestCase { - - @Test - public void testCacheBeanExtractAndLoad() { - - SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null); - BeanDescriptor desc = server.getBeanDescriptor(Customer.class); - - Customer c = new Customer(); - c.setId(98989); - c.setName("Rob"); - c.setCretime(new Timestamp(System.currentTimeMillis())); - c.setUpdtime(new Timestamp(System.currentTimeMillis())); - c.setStatus(Status.ACTIVE); - c.setSmallnote("somenote"); - - Address billingAddress = new Address(); - billingAddress.setId((short)12); - billingAddress.setCity("Auckland"); - billingAddress.setCountry(server.getReference(Country.class, "NZ")); - billingAddress.setLine1("92 Someplace Else"); - c.setBillingAddress(billingAddress); - - ((EntityBean)c)._ebean_getIntercept().setNewBeanForUpdate(); - - CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, (EntityBean)c); - - - Assert.assertNotNull(cacheData); - - Customer newCustomer = new Customer(); - newCustomer.setId(c.getId()); - CachedBeanDataToBean.load(desc, (EntityBean)newCustomer, cacheData); - - Assert.assertEquals(c.getId(), newCustomer.getId()); - Assert.assertEquals(c.getName(), newCustomer.getName()); - Assert.assertEquals(c.getStatus(), newCustomer.getStatus()); - Assert.assertEquals(c.getSmallnote(), newCustomer.getSmallnote()); - Assert.assertEquals(c.getCretime(), newCustomer.getCretime()); - Assert.assertEquals(c.getUpdtime(), newCustomer.getUpdtime()); - Assert.assertEquals(c.getBillingAddress().getId(), newCustomer.getBillingAddress().getId()); - - Assert.assertNotNull(newCustomer.getId()); - Assert.assertNotNull(newCustomer.getName()); - Assert.assertNotNull(newCustomer.getStatus()); - Assert.assertNotNull(newCustomer.getSmallnote()); - Assert.assertNotNull(newCustomer.getCretime()); - Assert.assertNotNull(newCustomer.getUpdtime()); - Assert.assertNotNull(newCustomer.getBillingAddress()); - Assert.assertNotNull(newCustomer.getBillingAddress().getId()); - - } - - - @Test - public void testCacheBeanExtractAndLoadWithEmbdedded() { - - SpiEbeanServer server = (SpiEbeanServer)Ebean.getServer(null); - BeanDescriptor desc = server.getBeanDescriptor(EPerson.class); - BeanPropertyAssocOne addressBeanProperty = (BeanPropertyAssocOne)desc.getBeanProperty("address"); - - EAddress address = new EAddress(); - address.setStreet("92 Someplace Else"); - address.setSuburb("Sandringham"); - address.setCity("Auckland"); - - EPerson person = new EPerson(); - person.setId(98989L); - person.setName("Rob"); - person.setAddress(address); - - CachedBeanData addressCacheData = (CachedBeanData)addressBeanProperty.getCacheDataValue((EntityBean) person); - - EPerson newPersonCheck = new EPerson(); - newPersonCheck.setId(98989L); - addressBeanProperty.setCacheDataValue((EntityBean) newPersonCheck, addressCacheData); - - EAddress newAddress = newPersonCheck.getAddress(); - Assert.assertEquals(address.getStreet(), newAddress.getStreet()); - Assert.assertEquals(address.getCity(), newAddress.getCity()); - Assert.assertEquals(address.getSuburb(), newAddress.getSuburb()); - - - - - CachedBeanData cacheData = desc.cacheBeanExtractData((EntityBean)person); - - Assert.assertNotNull(cacheData); - - EPerson newPerson = new EPerson(); - desc.cacheBeanLoadData((EntityBean)newPerson, cacheData); - - Assert.assertNotNull(newPerson.getId()); - Assert.assertNotNull(newPerson.getName()); - Assert.assertNotNull(newPerson.getAddress()); - - Assert.assertEquals(person.getId(), newPerson.getId()); - Assert.assertEquals(person.getName(), newPerson.getName()); - Assert.assertEquals(person.getAddress().getStreet(), newPerson.getAddress().getStreet()); - Assert.assertEquals(person.getAddress().getCity(), newPerson.getAddress().getCity()); - - } -} diff --git a/src/test/java/com/avaje/tests/basic/TestDeleteByIdCollection.java b/src/test/java/com/avaje/tests/basic/TestDeleteByIdCollection.java index 60167f2c4..a2e46d313 100644 --- a/src/test/java/com/avaje/tests/basic/TestDeleteByIdCollection.java +++ b/src/test/java/com/avaje/tests/basic/TestDeleteByIdCollection.java @@ -38,6 +38,7 @@ public class TestDeleteByIdCollection extends BaseTestCase { Ebean.deleteAll(Customer.class, ids); + awaitL2Cache(); c0Back = Ebean.find(Customer.class, c0.getId()); c1Back = Ebean.find(Customer.class, "" + c1.getId()); @@ -67,6 +68,7 @@ public class TestDeleteByIdCollection extends BaseTestCase { ids.add(order1.getId()); Ebean.deleteAll(Order.class, ids); + awaitL2Cache(); o0Back = Ebean.find(Order.class, order0.getId()); o1Back = Ebean.find(Order.class, order1.getId()); diff --git a/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java b/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java index 46a172ac7..cc75fb239 100644 --- a/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java +++ b/src/test/java/com/avaje/tests/cache/TestCacheCollectionIds.java @@ -67,6 +67,7 @@ public class TestCacheCollectionIds extends BaseTestCase { newContact.setCustomer(customer); Ebean.save(newContact); + awaitL2Cache(); int currentNumContacts2 = fetchCustomer(customer.getId()); Assert.assertEquals(currentNumContacts + 1, currentNumContacts2); @@ -121,6 +122,7 @@ public class TestCacheCollectionIds extends BaseTestCase { loadedBean.getCountries().add(Ebean.find(Country.class, "AU")); Ebean.save(loadedBean); + awaitL2Cache(); // Get the data to assert/check against OCachedBean result = Ebean.find(OCachedBean.class, cachedBean.getId()); @@ -169,6 +171,7 @@ public class TestCacheCollectionIds extends BaseTestCase { loadedBean.getCountries().add(Ebean.find(Country.class, "AU")); Ebean.save(loadedBean); + awaitL2Cache(); // Get the data to assert/check against OCachedBean result = Ebean.find(OCachedBean.class, cachedBean.getId()); @@ -222,9 +225,9 @@ public class TestCacheCollectionIds extends BaseTestCase { update.getCountries().add(Ebean.find(Country.class, "AU")); Ebean.update(update); - + awaitL2Cache(); + Assert.assertEquals("countries entry still there (but updated)", 1, cachedBeanCountriesCache.size()); - CachedManyIds cachedManyIds = (CachedManyIds) cachedBeanCountriesCache.get(update.getId()); diff --git a/src/test/java/com/avaje/tests/cache/TestCacheDelete.java b/src/test/java/com/avaje/tests/cache/TestCacheDelete.java index f534d7fc1..90b0620b9 100644 --- a/src/test/java/com/avaje/tests/cache/TestCacheDelete.java +++ b/src/test/java/com/avaje/tests/cache/TestCacheDelete.java @@ -1,45 +1,44 @@ package com.avaje.tests.cache; -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.tests.model.basic.OCachedBean; import com.avaje.tests.model.basic.OCachedBeanChild; +import org.junit.Assert; +import org.junit.Test; /** * Test class testing deleting/invalidating of cached beans */ public class TestCacheDelete extends BaseTestCase { - /** - * When deleting a cached entity all entities with a referenced OneToMany relation must also be invalidated! - */ - @Test - public void testCacheDeleteOneToMany() { - // arrange - OCachedBeanChild child = new OCachedBeanChild(); - OCachedBeanChild child2 = new OCachedBeanChild(); + /** + * When deleting a cached entity all entities with a referenced OneToMany relation must also be invalidated! + */ + @Test + public void testCacheDeleteOneToMany() { + // arrange + OCachedBeanChild child = new OCachedBeanChild(); + OCachedBeanChild child2 = new OCachedBeanChild(); - OCachedBean parentBean = new OCachedBean(); - parentBean.getChildren().add(child); - parentBean.getChildren().add(child2); - Ebean.save(parentBean); + OCachedBean parentBean = new OCachedBean(); + parentBean.getChildren().add(child); + parentBean.getChildren().add(child2); + Ebean.save(parentBean); - // confirm there are 2 children loaded from the parent - Assert.assertEquals(2, Ebean.find(OCachedBean.class, parentBean.getId()).getChildren().size()); + // confirm there are 2 children loaded from the parent + Assert.assertEquals(2, Ebean.find(OCachedBean.class, parentBean.getId()).getChildren().size()); - // ensure cache has been populated - Ebean.find(OCachedBeanChild.class, child.getId()); - child2 = Ebean.find(OCachedBeanChild.class, child2.getId()); - parentBean = Ebean.find(OCachedBean.class, parentBean.getId()); + // ensure cache has been populated + Ebean.find(OCachedBeanChild.class, child.getId()); + child2 = Ebean.find(OCachedBeanChild.class, child2.getId()); + parentBean = Ebean.find(OCachedBean.class, parentBean.getId()); - // act - Ebean.delete(child2); + // act + Ebean.delete(child2); + awaitL2Cache(); - // assert - OCachedBean beanFromCache = Ebean.find(OCachedBean.class, parentBean.getId()); - Assert.assertEquals(1, beanFromCache.getChildren().size()); - } + OCachedBean beanFromCache = Ebean.find(OCachedBean.class, parentBean.getId()); + Assert.assertEquals(1, beanFromCache.getChildren().size()); + } } diff --git a/src/test/java/com/avaje/tests/cache/TestCacheNaturalId.java b/src/test/java/com/avaje/tests/cache/TestCacheNaturalId.java index 60d4d0be0..21727cd2a 100644 --- a/src/test/java/com/avaje/tests/cache/TestCacheNaturalId.java +++ b/src/test/java/com/avaje/tests/cache/TestCacheNaturalId.java @@ -1,16 +1,18 @@ package com.avaje.tests.cache; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.cache.ServerCache; import com.avaje.ebean.cache.ServerCacheStatistics; import com.avaje.tests.model.basic.Contact; import com.avaje.tests.model.basic.ResetBasicData; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class TestCacheNaturalId extends BaseTestCase { @@ -23,7 +25,7 @@ public class TestCacheNaturalId extends BaseTestCase { List list = Ebean.find(Contact.class).setLoadBeanCache(true).findList(); - Assert.assertTrue(contactCache.size() > 0); + assertTrue(contactCache.size() > 0); String emailToSearch = null; for (Contact contact : list) { @@ -43,24 +45,25 @@ public class TestCacheNaturalId extends BaseTestCase { ServerCacheStatistics stats1 = contactCache.getStatistics(false); - Assert.assertNotNull(c0); - Assert.assertNotNull(c1); + assertNotNull(c0); + assertNotNull(c1); - Assert.assertEquals(1, stats0.getHitCount()); - Assert.assertEquals(2, stats1.getHitCount()); + assertEquals(1, stats0.getHitCount()); + assertEquals(2, stats1.getHitCount()); c1.setEmail("mychangedemail@what.com"); Ebean.save(c1); + awaitL2Cache(); Contact c2 = Ebean.find(Contact.class).where().eq("email", "mychangedemail@what.com") .findUnique(); ServerCacheStatistics stats2 = contactCache.getStatistics(false); - Assert.assertNotNull(c2); - Assert.assertEquals(c2.getId(), c1.getId()); - Assert.assertEquals(c0.getId(), c1.getId()); - Assert.assertTrue(stats2.getHitCount() > stats1.getHitCount()); + assertNotNull(c2); + assertEquals(c2.getId(), c1.getId()); + assertEquals(c0.getId(), c1.getId()); + assertTrue(stats2.getHitCount() > stats1.getHitCount()); } } diff --git a/src/test/java/com/avaje/tests/cache/TestQueryCacheCountry.java b/src/test/java/com/avaje/tests/cache/TestQueryCacheCountry.java index c1350529c..1765dd6d3 100644 --- a/src/test/java/com/avaje/tests/cache/TestQueryCacheCountry.java +++ b/src/test/java/com/avaje/tests/cache/TestQueryCacheCountry.java @@ -48,7 +48,8 @@ public class TestQueryCacheCountry extends BaseTestCase { Country nz = Ebean.find(Country.class, "NZ"); nz.setName("New Zealandia"); Ebean.save(nz); - + awaitL2Cache(); + statistics = queryCache.getStatistics(false); Assert.assertEquals(0, statistics.getSize()); diff --git a/src/test/java/com/avaje/tests/cache/TestQueryCacheInsert.java b/src/test/java/com/avaje/tests/cache/TestQueryCacheInsert.java index 8a74e3e87..82ad9f308 100644 --- a/src/test/java/com/avaje/tests/cache/TestQueryCacheInsert.java +++ b/src/test/java/com/avaje/tests/cache/TestQueryCacheInsert.java @@ -1,14 +1,14 @@ package com.avaje.tests.cache; -import java.util.List; - -import org.junit.Assert; -import org.junit.Test; - import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.EbeanServer; import com.avaje.tests.model.basic.EBasicVer; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; public class TestQueryCacheInsert extends BaseTestCase { @@ -24,13 +24,10 @@ public class TestQueryCacheInsert extends BaseTestCase { EBasicVer a2 = new EBasicVer(); server.save(a2); + awaitL2Cache(); List alist1 = server.find(EBasicVer.class).setUseQueryCache(true).findList(); - Assert.assertEquals(alist0.size() + 1, alist1.size()); - // List noQueryCacheList = server.find(EBasicVer.class) - // .setUseQueryCache(false) - // .findList(); - // Assert.assertTrue(sizeOne != noQueryCacheList.size()); + assertEquals(alist0.size() + 1, alist1.size()); } } diff --git a/src/test/java/com/avaje/tests/inheritance/cache/TestInheritanceCache.java b/src/test/java/com/avaje/tests/inheritance/cache/TestInheritanceCache.java new file mode 100644 index 000000000..9007f999a --- /dev/null +++ b/src/test/java/com/avaje/tests/inheritance/cache/TestInheritanceCache.java @@ -0,0 +1,42 @@ +package com.avaje.tests.inheritance.cache; + + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.tests.model.basic.cache.CInhOne; +import com.avaje.tests.model.basic.cache.CInhRoot; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class TestInheritanceCache extends BaseTestCase { + + @Test + public void test() { + + CInhOne one = new CInhOne(); + one.setLicenseNumber("O12"); + one.setDriver("Jimmy"); + one.setNotes("Hello"); + + Ebean.save(one); + + CInhRoot gotOne = Ebean.find(CInhRoot.class) + .setId(one.getId()) + .findUnique(); + + assertThat(gotOne).isInstanceOf(CInhOne.class); + + CInhRoot gotOneFromCache = Ebean.find(CInhRoot.class) + .setId(one.getId()) + .findUnique(); + + assertThat(gotOneFromCache).isInstanceOf(CInhOne.class); + + CInhRoot refOne = Ebean.getReference(CInhRoot.class, one.getId()); + assertThat(refOne).isInstanceOf(CInhOne.class); + + CInhRoot refOneSub = Ebean.getReference(CInhOne.class, one.getId()); + assertThat(refOneSub).isNotNull(); + } +} diff --git a/src/test/java/com/avaje/tests/insert/TestInsertCollection.java b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java index 5e1e41260..40b2fcc61 100644 --- a/src/test/java/com/avaje/tests/insert/TestInsertCollection.java +++ b/src/test/java/com/avaje/tests/insert/TestInsertCollection.java @@ -39,6 +39,7 @@ public class TestInsertCollection extends BaseTestCase { cust2.setName("bob-changed"); Ebean.updateAll(customers); + awaitL2Cache(); Customer cust1Check2 = Ebean.find(Customer.class, cust1.getId()); Assert.assertEquals("jim-changed", cust1Check2.getName()); @@ -55,7 +56,8 @@ public class TestInsertCollection extends BaseTestCase { saveList.add(cust3); Ebean.saveAll(saveList); - + awaitL2Cache(); + Customer cust1Check3 = Ebean.find(Customer.class, cust1.getId()); Assert.assertEquals("jim-updated", cust1Check3.getName()); @@ -68,6 +70,7 @@ public class TestInsertCollection extends BaseTestCase { deleteList.add(cust2Check2); Ebean.deleteAll(deleteList); + awaitL2Cache(); Assert.assertNull(Ebean.find(Customer.class, cust1Check3.getId())); Assert.assertNull(Ebean.find(Customer.class, cust2Check2.getId())); diff --git a/src/test/java/com/avaje/tests/model/basic/cache/CInhOne.java b/src/test/java/com/avaje/tests/model/basic/cache/CInhOne.java new file mode 100644 index 000000000..648315480 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/cache/CInhOne.java @@ -0,0 +1,34 @@ +package com.avaje.tests.model.basic.cache; + +import com.avaje.ebean.annotation.CacheStrategy; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Inheritance; + +@CacheStrategy +@Entity +@Inheritance +@DiscriminatorValue("O") +public class CInhOne extends CInhRoot { + + private String driver; + + private String notes; + + public String getDriver() { + return driver; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } +} diff --git a/src/test/java/com/avaje/tests/model/basic/cache/CInhRoot.java b/src/test/java/com/avaje/tests/model/basic/cache/CInhRoot.java new file mode 100644 index 000000000..8563f13cf --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/cache/CInhRoot.java @@ -0,0 +1,25 @@ +package com.avaje.tests.model.basic.cache; + +import com.avaje.ebean.annotation.CacheStrategy; +import com.avaje.tests.model.basic.BasicDomain; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.Entity; +import javax.persistence.Inheritance; + +@CacheStrategy +@Entity +@Inheritance +@DiscriminatorColumn(length = 3) +public abstract class CInhRoot extends BasicDomain { + + private String licenseNumber; + + public String getLicenseNumber() { + return licenseNumber; + } + + public void setLicenseNumber(String licenseNumber) { + this.licenseNumber = licenseNumber; + } +} diff --git a/src/test/java/com/avaje/tests/model/basic/cache/CInhTwo.java b/src/test/java/com/avaje/tests/model/basic/cache/CInhTwo.java new file mode 100644 index 000000000..dbc1a1726 --- /dev/null +++ b/src/test/java/com/avaje/tests/model/basic/cache/CInhTwo.java @@ -0,0 +1,24 @@ +package com.avaje.tests.model.basic.cache; + +import com.avaje.ebean.annotation.CacheStrategy; + +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Inheritance; + +@CacheStrategy +@Entity +@Inheritance +@DiscriminatorValue("T") +public class CInhTwo extends CInhRoot { + + private String action; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } +} diff --git a/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java b/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java index cee2d916f..d956b9030 100644 --- a/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java +++ b/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java @@ -56,6 +56,7 @@ public class TestCustomerFinder extends BaseTestCase { assertThat(customer.getName()).isEqualTo(customer2.getName()); Customer.find.deleteById(customer.getId()); + awaitL2Cache(); Customer notThere = Customer.find.byId(customer.getId()); assertThat(notThere).isNull(); diff --git a/src/test/java/com/avaje/tests/update/TestJsonStatelessUpdate.java b/src/test/java/com/avaje/tests/update/TestJsonStatelessUpdate.java index 532bd93d1..9678eb7f0 100644 --- a/src/test/java/com/avaje/tests/update/TestJsonStatelessUpdate.java +++ b/src/test/java/com/avaje/tests/update/TestJsonStatelessUpdate.java @@ -49,6 +49,7 @@ public class TestJsonStatelessUpdate extends BaseTestCase { // as it thinks it should INSERT master rather than UPDATE master Ebean.update(two2); + awaitL2Cache(); // confirm the properties where updated as expected diff --git a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java index 02f20f74d..9c832c6cc 100644 --- a/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java +++ b/src/test/java/com/avaje/tests/update/TestStatelessUpdate.java @@ -23,19 +23,11 @@ import static org.junit.Assert.assertNull; public class TestStatelessUpdate extends BaseTestCase { - private EbeanServer server; - - @Before - public void setUp() { - server = Ebean.getServer(null); - } + private EbeanServer server = server(); @Test public void test() { - // GlobalProperties.put("ebean.defaultUpdateNullProperties", "true"); - // GlobalProperties.put("ebean.defaultDeleteMissingChildren", "false"); - EBasic e = new EBasic(); e.setName("something"); e.setStatus(Status.NEW); diff --git a/src/test/java/com/avaje/tests/update/TestUpdatePartial.java b/src/test/java/com/avaje/tests/update/TestUpdatePartial.java index 4d8b4749c..6c6215627 100644 --- a/src/test/java/com/avaje/tests/update/TestUpdatePartial.java +++ b/src/test/java/com/avaje/tests/update/TestUpdatePartial.java @@ -23,6 +23,7 @@ public class TestUpdatePartial extends BaseTestCase { checkDbStatusValue(c.getId(), "A"); Customer c2 = Ebean.find(Customer.class) + .setUseCache(false) .select("status, smallnote") .setId(c.getId()) .findUnique(); @@ -34,6 +35,7 @@ public class TestUpdatePartial extends BaseTestCase { checkDbStatusValue(c.getId(), "I"); Customer c3 = Ebean.find(Customer.class) + .setUseCache(false) .select("status") .setId(c.getId()) .findUnique(); @@ -58,17 +60,15 @@ public class TestUpdatePartial extends BaseTestCase { */ @Test public void testWithoutChangesAndVersionColumn() { - // arrange + Customer customer = new Customer(); customer.setName("something"); Ebean.save(customer); - // act Customer customerWithoutChanges = Ebean.find(Customer.class, customer.getId()); Ebean.save(customerWithoutChanges); - // assert assertEquals(customer.getUpdtime().getTime(), customerWithoutChanges.getUpdtime().getTime()); } } diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index c49f3fd5b..6fe757245 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -84,10 +84,10 @@ - - - - + + + +