From c66d248e963dc1854256f4206cea1c233596ef4a Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 29 Jul 2021 15:42:16 +1200 Subject: [PATCH] Add MutableValueNext to replace mutableContent such that hash compute is only done once Adds MutableValueInfo.nextDirty() to replace the isEqualToJson() method. The next is computed once and stored. BindablePropertyJsonUpdate makes the .mutableNext(propertyIndex) call to move the next MutableValueInfo and return the json content. --- .../io/ebean/bean/EntityBeanIntercept.java | 34 +++--- .../java/io/ebean/bean/MutableValueInfo.java | 11 +- .../java/io/ebean/bean/MutableValueNext.java | 17 +++ .../server/deploy/BeanPropertyJsonMapper.java | 102 ++++++++++++++---- .../dmlbind/BindablePropertyJsonUpdate.java | 8 +- 5 files changed, 125 insertions(+), 47 deletions(-) create mode 100644 ebean-api/src/main/java/io/ebean/bean/MutableValueNext.java 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); } }