diff --git a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java index 99e9334c1..78d8e5f8c 100644 --- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java +++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java @@ -105,7 +105,7 @@ public final class EntityBeanIntercept implements Serializable { * Holds json content determined at point of dirty check. * Stored here on dirty check such that we only convert to json once. */ - private String[] mutableContent; + private MutableValueNext[] mutableNext; /** * Create a intercept with a given entity. @@ -395,7 +395,7 @@ public final class EntityBeanIntercept implements Serializable { this.owner._ebean_setEmbeddedLoaded(); this.lazyLoadProperty = -1; this.origValues = null; - this.mutableContent = null; + this.mutableNext = null; for (int i = 0; i < flags.length; i++) { flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET); } @@ -1203,23 +1203,29 @@ public final class EntityBeanIntercept implements Serializable { } /** - * Return the [json] content of a mutable value. - */ - public String mutableContent(int propertyIndex) { - return mutableContent == null ? null : mutableContent[propertyIndex]; - } - - /** - * Set the [json] content of a mutable property. + * Dirty detection set the next mutable property content and info . *
* Set here as the mutable property dirty detection is based on json content comparison. * We only want to perform the json serialisation once so storing it here as part of * dirty detection so that we can get it back to bind in insert or update etc. */ - public void mutableContent(int propertyIndex, String content) { - if (mutableContent == null) { - mutableContent = new String[flags.length]; + public void mutableNext(int propertyIndex, MutableValueNext next) { + if (mutableNext == null) { + mutableNext = new MutableValueNext[flags.length]; } - mutableContent[propertyIndex] = content; + mutableNext[propertyIndex] = next; } + + /** + * Update the 'next' mutable info returning the content that was obtained via dirty detection. + */ + public String mutableNext(int propertyIndex) { + if (mutableNext == null) { + return null; + } + final MutableValueNext next = mutableNext[propertyIndex]; + mutableInfo(propertyIndex, next.info()); + return next.content(); + } + } diff --git a/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java b/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java index de8cbee6d..0f9c223aa 100644 --- a/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java +++ b/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java @@ -12,12 +12,15 @@ package io.ebean.bean; public interface MutableValueInfo { /** - * Compares the given json to an internal value. Can be a hash/checksum comparison - * or a plain JSON string comparison (based on {@code @DbJson(keepSource)}). + * Compares the given json returning null if deemed unchanged or returning + * the MutableValueNext to use if deemed dirty/changed. + *
+ * Returning MutableValueNext allows an implementation based on hash/checksum + * to only perform that computation once. * - * @return true if the value is considered unchanged (when comparing in json form). + * @return Null if deemed unchanged or the MutableValueNext if deemed changed. */ - boolean isEqualToJson(String json); + MutableValueNext nextDirty(String json); /** * Compares the given object to an internal value. diff --git a/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java b/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java new file mode 100644 index 000000000..401d3c223 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java @@ -0,0 +1,17 @@ +package io.ebean.bean; + +/** + * Represents a next value to use for mutable content properties (DbJson with jackson beans). + */ +public interface MutableValueNext { + + /** + * Return the next content to use. Provided such that we serialise to json once. + */ + String content(); + + /** + * Return the next MutableValueInfo to use after an update. + */ + MutableValueInfo info(); +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java index fca7b790b..d3a683e48 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.MutableValueNext; import io.ebean.bean.MutableValueInfo; import io.ebean.core.type.DataReader; import io.ebean.core.type.ScalarType; @@ -36,6 +37,19 @@ public class BeanPropertyJsonMapper extends BeanProperty { } } + /** + * Next when no prior MutableValueInfo. + */ + private MutableValueNext next(String json) { + if (keepSource) { + return new SourceMutableValue(scalarType, json); + } else if (dirtyDetection) { + return new NextPair(json, new ChecksumMutableValue(scalarType, json)); + } else { + throw new IllegalStateException("Never get here"); + } + } + /** * Return true if the mutable value is considered dirty. * This is only used for 'mutable' scalar types like hstore etc. @@ -43,10 +57,17 @@ public class BeanPropertyJsonMapper extends BeanProperty { @Override boolean isDirtyValue(Object value, EntityBeanIntercept ebi) { // dirty detection based on json content or checksum of json content + // only perform serialisation to json once final String json = scalarType.format(value); final MutableValueInfo oldHash = ebi.mutableInfo(propertyIndex); - if (oldHash == null || !oldHash.isEqualToJson(json)) { - ebi.mutableContent(propertyIndex, json); // so we only convert to json once + if (oldHash == null) { + ebi.mutableNext(propertyIndex, next(json)); + return true; + } + // only perform compute of checksum/hash once (if checksum based) + final MutableValueNext next = oldHash.nextDirty(json); + if (next != null) { + ebi.mutableNext(propertyIndex, next); return true; } return false; @@ -72,34 +93,59 @@ public class BeanPropertyJsonMapper extends BeanProperty { } } + private static final class NextPair implements MutableValueNext { + + private final String json; + private final MutableValueInfo next; + + NextPair(String json, MutableValueInfo next) { + this.json = json; + this.next = next; + } + + @Override + public String content() { + return json; + } + + @Override + public MutableValueInfo info() { + return next; + } + } + /** - * Hold checksum of json source content. + * Hold checksum of json source content to use for dirty detection. *
- * Dirty detection based on checksum difference on json form. * Does not support rebuilding 'oldValue' as no original json content. */ - private static class ChecksumMutableValue implements MutableValueInfo { + private static final class ChecksumMutableValue implements MutableValueInfo { private final ScalarType> parent; private final long checksum; ChecksumMutableValue(ScalarType> parent, String json) { this.parent = parent; - this.checksum = checksum(json); + this.checksum = Checksum.checksum(json); } - private long checksum(String json) { - return Checksum.checksum(json); + /** + * Create with pre-computed checksum. + */ + ChecksumMutableValue(ScalarType> parent, long checksum) { + this.parent = parent; + this.checksum = checksum; + } + + @Override + public MutableValueNext nextDirty(String json) { + final long nextChecksum = Checksum.checksum(json); + return nextChecksum == checksum ? null : new NextPair(json, new ChecksumMutableValue(parent, nextChecksum)); } @Override public boolean isEqualToObject(Object obj) { - return isEqualToJson(parent.format(obj)); - } - - @Override - public boolean isEqualToJson(String json) { - return checksum(json) == checksum; + return Checksum.checksum(parent.format(obj)) == checksum; } @Override @@ -109,9 +155,9 @@ public class BeanPropertyJsonMapper extends BeanProperty { } /** - * Hold original json source content. This supports rebuilding the 'oldValue'. + * Hold json source content. This supports rebuilding the 'oldValue'. */ - private static class SourceMutableValue implements MutableValueInfo { + private static final class SourceMutableValue implements MutableValueInfo, MutableValueNext { private final String originalJson; private final ScalarType> parent; @@ -122,13 +168,13 @@ public class BeanPropertyJsonMapper extends BeanProperty { } @Override - public boolean isEqualToObject(Object obj) { - return isEqualToJson(parent.format(obj)); + public MutableValueNext nextDirty(String json) { + return Objects.equals(originalJson, json) ? null : new SourceMutableValue(parent, json); } @Override - public boolean isEqualToJson(String json) { - return Objects.equals(originalJson, json); + public boolean isEqualToObject(Object obj) { + return Objects.equals(originalJson, parent.format(obj)); } @Override @@ -136,16 +182,26 @@ public class BeanPropertyJsonMapper extends BeanProperty { // rebuild the 'oldValue' for change log etc return parent.parse(originalJson); } + + @Override + public String content() { + return originalJson; + } + + @Override + public MutableValueInfo info() { + return this; + } } /** * No dirty detection on json content. */ - private static class NoDirtyDetection implements MutableValueInfo { + private static final class NoDirtyDetection implements MutableValueInfo { @Override - public boolean isEqualToJson(String json) { - return true; // treat as not dirty + public MutableValueNext nextDirty(String json) { + return null; // treat as not dirty } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java index 5a94d0e0e..cfa056a63 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java @@ -1,7 +1,6 @@ package io.ebeaninternal.server.persist.dmlbind; import io.ebean.bean.EntityBean; -import io.ebean.bean.MutableValueInfo; import io.ebeaninternal.server.deploy.BeanProperty; import java.sql.SQLException; @@ -26,11 +25,8 @@ class BindablePropertyJsonUpdate extends BindableProperty { if (bean == null) { request.bind(null, prop); } else { - // on update store hash and push json - final String json = bean._ebean_getIntercept().mutableContent(propertyIndex); - final MutableValueInfo hash = prop.createMutableInfo(json); - bean._ebean_getIntercept().mutableInfo(propertyIndex, hash); - request.pushJson(json); + // update mutableInfo and push json + request.pushJson(bean._ebean_getIntercept().mutableNext(propertyIndex)); request.bind(prop.getValue(bean), prop); } }