diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml
index d3868ff91..503c2f2c5 100644
--- a/ebean-api/pom.xml
+++ b/ebean-api/pom.xml
@@ -50,7 +50,7 @@
io.ebeanebean-annotation
- 7.0
+ 7.2
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 28a088ce6..78d8e5f8c 100644
--- a/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java
+++ b/ebean-api/src/main/java/io/ebean/bean/EntityBeanIntercept.java
@@ -51,6 +51,11 @@ public final class EntityBeanIntercept implements Serializable {
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
+ /**
+ * Flags indicating if the mutable hash is set.
+ */
+ private static final byte FLAG_MUTABLE_HASH_SET = 16;
+
private transient final ReentrantLock lock = new ReentrantLock();
private transient NodeUsageCollector nodeUsageCollector;
private transient PersistenceContext persistenceContext;
@@ -91,6 +96,17 @@ public final class EntityBeanIntercept implements Serializable {
private Object ownerId;
private int sortOrder;
+ /**
+ * Holds information of json loaded jackson beans (e.g. the original json or checksum).
+ */
+ private MutableValueInfo[] mutableInfo;
+
+ /**
+ * Holds json content determined at point of dirty check.
+ * Stored here on dirty check such that we only convert to json once.
+ */
+ private MutableValueNext[] mutableNext;
+
/**
* Create a intercept with a given entity.
*/
@@ -228,6 +244,17 @@ public final class EntityBeanIntercept implements Serializable {
* if any embedded beans are either new or dirty (and hence need saving).
*/
public boolean isDirty() {
+ if (dirty) {
+ return true;
+ }
+ if (mutableInfo != null) {
+ for (int i = 0; i < mutableInfo.length; i++) {
+ if (mutableInfo[i] != null && !mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
+ dirty = true;
+ break;
+ }
+ }
+ }
return dirty;
}
@@ -368,8 +395,9 @@ public final class EntityBeanIntercept implements Serializable {
this.owner._ebean_setEmbeddedLoaded();
this.lazyLoadProperty = -1;
this.origValues = null;
+ this.mutableNext = null;
for (int i = 0; i < flags.length; i++) {
- flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
+ flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET);
}
this.dirty = false;
}
@@ -442,6 +470,10 @@ public final class EntityBeanIntercept implements Serializable {
* Return the original value that was changed via an update.
*/
public Object getOrigValue(int propertyIndex) {
+ if ((flags[propertyIndex] & (FLAG_ORIG_VALUE_SET | FLAG_MUTABLE_HASH_SET)) == FLAG_MUTABLE_HASH_SET) {
+ // mutable hash set, but not ORIG_VALUE
+ setOriginalValue(propertyIndex, mutableInfo[propertyIndex].get());
+ }
if (origValues == null) {
return null;
}
@@ -638,7 +670,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyNames(Set props, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ if (isChangedProp(i)) {
// the property has been changed on this bean
props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
@@ -656,7 +688,7 @@ public final class EntityBeanIntercept implements Serializable {
String[] names = owner._ebean_getPropertyNames();
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ if (isChangedProp(i)) {
if (propertyNames.contains(names[i])) {
return true;
}
@@ -684,7 +716,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(Map dirtyValues, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ if (isChangedProp(i)) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
@@ -706,7 +738,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ if (isChangedProp(i)) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
@@ -741,7 +773,7 @@ public final class EntityBeanIntercept implements Serializable {
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
- if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ if ((flags[i] & FLAG_CHANGED_PROP) != 0) { // we do not check against mutablecontent here.
sb.append(i).append(',');
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
@@ -1138,4 +1170,62 @@ public final class EntityBeanIntercept implements Serializable {
}
return ret;
}
+
+ private boolean isChangedProp(int i) {
+ if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
+ return true;
+ } else if (mutableInfo == null || mutableInfo[i] == null || mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
+ return false;
+ } else {
+ // mark for change
+ flags[i] |= FLAG_CHANGED_PROP;
+ dirty = true; // this makes the bean automatically dirty!
+ return true;
+ }
+ }
+
+ /**
+ * Return the MutableValueInfo for the given property or null.
+ */
+ public MutableValueInfo mutableInfo(int propertyIndex) {
+ return mutableInfo == null ? null : mutableInfo[propertyIndex];
+ }
+
+ /**
+ * Set the MutableValueInfo for the given property.
+ */
+ public void mutableInfo(int propertyIndex, MutableValueInfo info) {
+ if (mutableInfo == null) {
+ mutableInfo = new MutableValueInfo[flags.length];
+ }
+ flags[propertyIndex] |= FLAG_MUTABLE_HASH_SET;
+ mutableInfo[propertyIndex] = info;
+ }
+
+ /**
+ * 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 mutableNext(int propertyIndex, MutableValueNext next) {
+ if (mutableNext == null) {
+ mutableNext = new MutableValueNext[flags.length];
+ }
+ 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
new file mode 100644
index 000000000..0f9c223aa
--- /dev/null
+++ b/ebean-api/src/main/java/io/ebean/bean/MutableValueInfo.java
@@ -0,0 +1,42 @@
+package io.ebean.bean;
+
+/**
+ * Holds information on mutable values (like plain beans stored as json).
+ *
+ * Used internally in EntityBeanIntercept for dirty detection on mutable values.
+ * Typically dirty detection is based on a hash/checksum of json content or the
+ * original json content itself.
+ *
+ * Refer to the mapping options {@code @DbJson(dirtyDetection)} and {@code @DbJson(keepSource)}.
+ */
+public interface MutableValueInfo {
+
+ /**
+ * 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 Null if deemed unchanged or the MutableValueNext if deemed changed.
+ */
+ MutableValueNext nextDirty(String json);
+
+ /**
+ * Compares the given object to an internal value.
+ *
+ * This is used to support changelog/beanState. The implementation can serialize the
+ * object into json form and compare it against the original json.
+ */
+ boolean isEqualToObject(Object obj);
+
+ /**
+ * Creates a new instance from the internal json string.
+ *
+ * This is used to provide an original/old value for change logging / persist listeners.
+ * This is only available for properties that have {@code @DbJson(keepSource=true)}.
+ */
+ default Object get() {
+ return null;
+ }
+}
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-type/src/main/java/io/ebean/core/type/DataBinder.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java
index be6a3b559..b08bb529a 100644
--- a/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java
+++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataBinder.java
@@ -163,4 +163,15 @@ public interface DataBinder {
* Bind an array value.
*/
void setArray(String arrayType, Object[] elements) throws SQLException;
+
+ /**
+ * Push json from dirty detection to be available for binding.
+ */
+ void pushJson(String json);
+
+ /**
+ * Pop json made during dirty detection for scalarType binding.
+ */
+ String popJson();
+
}
diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java
index a8ace12be..ef61b9122 100644
--- a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java
+++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java
@@ -48,4 +48,14 @@ public interface DataReader {
Object getObject() throws SQLException;
InputStream getBinaryStream() throws SQLException;
+
+ /**
+ * Push json from dirty detection to be available for binding.
+ */
+ void pushJson(String json);
+
+ /**
+ * Pop json made during dirty detection for scalarType binding.
+ */
+ String popJson();
}
diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java b/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java
index a971a0115..6c01811c5 100644
--- a/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java
+++ b/ebean-core-type/src/main/java/io/ebean/core/type/ScalarType.java
@@ -2,6 +2,7 @@ package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
+
import io.ebean.text.StringFormatter;
import io.ebean.text.StringParser;
@@ -34,6 +35,10 @@ import java.sql.SQLException;
*/
public interface ScalarType extends StringParser, StringFormatter, ScalarDataReader {
+ default boolean isJsonMapper() {
+ return false;
+ }
+
/**
* Return true if this is a binary type and can not support parse() and format() from/to string.
* This allows Ebean to optimise marshalling types to string.
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
index 63599b7ad..8f0fea0f2 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -2022,7 +2022,7 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType {
public boolean isTableManaged(String tableName) {
return owner.isTableManaged(tableName);
}
-
+
/**
* Return the order column property.
*/
@@ -3198,9 +3198,9 @@ public class BeanDescriptor implements BeanType, STreeType, SpiBeanType {
public void checkMutableProperties(EntityBeanIntercept ebi) {
for (BeanProperty beanProperty : propertiesMutable) {
int propertyIndex = beanProperty.getPropertyIndex();
- if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) {
+ if (ebi.isLoadedProperty(propertyIndex)) {
Object value = beanProperty.getValue(ebi.getOwner());
- if (value != null && beanProperty.isDirtyValue(value)) {
+ if (beanProperty.checkMutable(value, ebi.isDirtyProperty(propertyIndex), ebi)) {
// mutable scalar value which is considered dirty so mark
// it as such so that it is included in an update
ebi.markPropertyAsChanged(propertyIndex);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
index 8793942b6..a33d183ea 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
+import io.ebean.bean.MutableValueInfo;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbEncryptFunction;
@@ -13,6 +14,7 @@ import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
import io.ebean.plugin.Property;
import io.ebean.text.StringParser;
+import io.ebean.text.TextException;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
@@ -31,11 +33,7 @@ import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
-import io.ebeaninternal.server.type.DataBind;
-import io.ebeaninternal.server.type.LocalEncryptedType;
-import io.ebeaninternal.server.type.ScalarTypeBoolean;
-import io.ebeaninternal.server.type.ScalarTypeEnum;
-import io.ebeaninternal.server.type.ScalarTypeLogicalType;
+import io.ebeaninternal.server.type.*;
import io.ebeaninternal.util.ValueUtil;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
@@ -632,6 +630,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
setValue(bean, value);
}
return value;
+ } catch (TextException e) {
+ throw e;
} catch (Exception e) {
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
}
@@ -642,15 +642,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
- try {
- Object value = scalarType.read(ctx.getDataReader());
- if (bean != null) {
- setValue(bean, value);
- }
- return value;
- } catch (Exception e) {
- throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
- }
+ return readSet(ctx.getDataReader(), bean);
}
@SuppressWarnings("unchecked")
@@ -828,6 +820,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return scalarType.parse(value);
}
+ /**
+ * creates a mutableHash for the given JSON value.
+ */
+ public MutableValueInfo createMutableInfo(String json) {
+ throw new UnsupportedOperationException();
+ }
+
/**
* Read the value for this property from L2 cache entry and set it to the bean.
*
@@ -909,7 +908,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the name of the property.
*/
- @Override @Nonnull
+ @Override
+ @Nonnull
public String getName() {
return name;
}
@@ -1018,8 +1018,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return true if the mutable value is considered dirty.
* This is only used for 'mutable' scalar types like hstore etc.
*/
- boolean isDirtyValue(Object value) {
- return scalarType.isDirty(value);
+ boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) {
+ return alreadyDirty || value != null && scalarType.isDirty(value);
}
/**
@@ -1281,7 +1281,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
@Override
public Object localEncrypt(Object value) {
- return ((LocalEncryptedType)scalarType).localEncrypt(value);
+ return ((LocalEncryptedType) scalarType).localEncrypt(value);
}
/**
@@ -1394,7 +1394,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the property type.
*/
- @Override @Nonnull
+ @Override
+ @Nonnull
public Class> getPropertyType() {
return propertyType;
}
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
new file mode 100644
index 000000000..bc7392287
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyJsonMapper.java
@@ -0,0 +1,195 @@
+package io.ebeaninternal.server.deploy;
+
+import io.ebean.annotation.MutationDetection;
+import io.ebean.bean.EntityBean;
+import io.ebean.bean.EntityBeanIntercept;
+import io.ebean.bean.MutableValueInfo;
+import io.ebean.bean.MutableValueNext;
+import io.ebean.core.type.DataReader;
+import io.ebean.core.type.ScalarType;
+import io.ebean.text.TextException;
+import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
+import io.ebeaninternal.server.util.Checksum;
+
+import javax.persistence.PersistenceException;
+import java.sql.SQLException;
+import java.util.Objects;
+
+/**
+ * Handle json property with MutationDetection of SOURCE or HASH only.
+ */
+public class BeanPropertyJsonMapper extends BeanProperty {
+
+ private final boolean sourceDetection;
+
+ public BeanPropertyJsonMapper(BeanDescriptor> desc, DeployBeanProperty deployProp) {
+ super(desc, deployProp);
+ this.sourceDetection = deployProp.getMutationDetection() == MutationDetection.SOURCE;
+ }
+
+ @Override
+ public MutableValueInfo createMutableInfo(String json) {
+ if (sourceDetection) {
+ return new SourceMutableValue(scalarType, json);
+ } else {
+ return new ChecksumMutableValue(scalarType, json);
+ }
+ }
+
+ /**
+ * Next when no prior MutableValueInfo.
+ */
+ private MutableValueNext next(String json) {
+ if (sourceDetection) {
+ return new SourceMutableValue(scalarType, json);
+ } else {
+ return new NextPair(json, new ChecksumMutableValue(scalarType, json));
+ }
+ }
+
+ /**
+ * Return true if the json property is considered dirty.
+ */
+ @Override
+ boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) {
+ // mutation 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) {
+ if (value == null) {
+ return false; // no change, still 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;
+ }
+
+ @Override
+ public Object readSet(DataReader reader, EntityBean bean) throws SQLException {
+ try {
+ Object value = scalarType.read(reader);
+ if (bean != null) {
+ setValue(bean, value);
+ String json = reader.popJson();
+ if (json != null) {
+ final MutableValueInfo hash = createMutableInfo(json);
+ bean._ebean_getIntercept().mutableInfo(propertyIndex, hash);
+ }
+ }
+ return value;
+ } catch (TextException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
+ }
+ }
+
+ 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 to use for dirty detection.
+ *
+ * Does not support rebuilding 'oldValue' as no original json content.
+ */
+ 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.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 Checksum.checksum(parent.format(obj)) == checksum;
+ }
+
+ @Override
+ public Object get() {
+ return null; // cannot create object from json
+ }
+ }
+
+ /**
+ * Hold json source content. This supports rebuilding the 'oldValue'.
+ */
+ private static final class SourceMutableValue implements MutableValueInfo, MutableValueNext {
+
+ private final String originalJson;
+ private final ScalarType> parent;
+
+ SourceMutableValue(ScalarType> parent, String json) {
+ this.parent = parent;
+ this.originalJson = json;
+ }
+
+ @Override
+ public MutableValueNext nextDirty(String json) {
+ return Objects.equals(originalJson, json) ? null : new SourceMutableValue(parent, json);
+ }
+
+ @Override
+ public boolean isEqualToObject(Object obj) {
+ return Objects.equals(originalJson, parent.format(obj));
+ }
+
+ @Override
+ public Object get() {
+ // rebuild the 'oldValue' for change log etc
+ return parent.parse(originalJson);
+ }
+
+ @Override
+ public String content() {
+ return originalJson;
+ }
+
+ @Override
+ public MutableValueInfo info() {
+ return this;
+ }
+ }
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
index e20c898ce..3da06d559 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanProperty.java
@@ -1,18 +1,6 @@
package io.ebeaninternal.server.deploy.meta;
-import io.ebean.annotation.CreatedTimestamp;
-import io.ebean.annotation.DocCode;
-import io.ebean.annotation.DocProperty;
-import io.ebean.annotation.DocSortable;
-import io.ebean.annotation.Formula;
-import io.ebean.annotation.Platform;
-import io.ebean.annotation.SoftDelete;
-import io.ebean.annotation.UpdatedTimestamp;
-import io.ebean.annotation.WhenCreated;
-import io.ebean.annotation.WhenModified;
-import io.ebean.annotation.Where;
-import io.ebean.annotation.WhoCreated;
-import io.ebean.annotation.WhoModified;
+import io.ebean.annotation.*;
import io.ebean.config.ScalarTypeConverter;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbEncrypt;
@@ -39,7 +27,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Types;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -109,6 +96,7 @@ public class DeployBeanProperty {
private boolean jsonSerialize = true;
private boolean jsonDeserialize = true;
+ private MutationDetection mutationDetection;
private boolean dbEncrypted;
private DbEncryptFunction dbEncryptFunction;
@@ -327,6 +315,17 @@ public class DeployBeanProperty {
this.jsonDeserialize = jsonDeserialize;
}
+ public MutationDetection getMutationDetection() {
+ if (mutationDetection == null) {
+ mutationDetection = MutationDetection.DEFAULT;
+ }
+ return mutationDetection;
+ }
+
+ public void setMutationDetection(MutationDetection dirtyDetection) {
+ this.mutationDetection = dirtyDetection;
+ }
+
/**
* Return the sortOrder for the properties.
*/
@@ -1201,4 +1200,8 @@ public class DeployBeanProperty {
return false;
}
+ boolean isJsonMapper() {
+ return scalarType != null && scalarType.isJsonMapper();
+ }
+
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
index f6280b260..90503c93f 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanPropertyLists.java
@@ -1,15 +1,7 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.bean.EntityBean;
-import io.ebeaninternal.server.deploy.BeanDescriptor;
-import io.ebeaninternal.server.deploy.BeanDescriptorMap;
-import io.ebeaninternal.server.deploy.BeanProperty;
-import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
-import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
-import io.ebeaninternal.server.deploy.BeanPropertyIdClass;
-import io.ebeaninternal.server.deploy.BeanPropertyOrderColumn;
-import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
-import io.ebeaninternal.server.deploy.InheritInfo;
+import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.type.ScalarTypeString;
@@ -30,47 +22,28 @@ public class DeployBeanPropertyLists {
private static final NoopSetter NOOP_SETTER = new NoopSetter();
private BeanProperty versionProperty;
-
private BeanProperty unmappedJson;
-
private BeanProperty draft;
-
private BeanProperty draftDirty;
-
private BeanProperty tenant;
-
private final BeanDescriptor> desc;
-
private final LinkedHashMap propertyMap;
-
private BeanProperty id;
private final List local = new ArrayList<>();
-
private final List mutable = new ArrayList<>();
-
private final List> manys = new ArrayList<>();
-
private final List nonManys = new ArrayList<>();
-
private final List aggs = new ArrayList<>();
-
private final List> ones = new ArrayList<>();
-
private final List> onesImported = new ArrayList<>();
-
private final List> embedded = new ArrayList<>();
-
private final List baseScalar = new ArrayList<>();
-
private final List transients = new ArrayList<>();
-
private final List nonTransients = new ArrayList<>();
-
private final BeanPropertyAssocOne> unidirectional;
private final BeanProperty orderColumn;
- @SuppressWarnings({"unchecked"})
public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor> desc, DeployBeanDescriptor> deploy) {
this.desc = desc;
@@ -86,7 +59,7 @@ public class DeployBeanPropertyLists {
this.orderColumn = deployOrderColumn != null ? new BeanPropertyOrderColumn(desc, deployOrderColumn) : null;
DeployBeanPropertyAssocOne> deployUnidirectional = deploy.getUnidirectional();
- this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne(owner, desc, deployUnidirectional);
+ this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne<>(owner, desc, deployUnidirectional);
this.propertyMap = new LinkedHashMap<>();
@@ -127,7 +100,7 @@ public class DeployBeanPropertyLists {
}
if (orderColumn != null) {
- orderColumn.setDeployOrder(order++);
+ orderColumn.setDeployOrder(order);
allocateToList(orderColumn);
propertyMap.put(orderColumn.getName(), orderColumn);
}
@@ -154,7 +127,6 @@ public class DeployBeanPropertyLists {
}
private void setImportedPrimaryKeysFor(DeployBeanDescriptor> deploy, DeployBeanPropertyAssocOne> id) {
-
for (DeployBeanProperty prop : id.getTargetDeploy().properties()) {
DeployBeanProperty match = findImported(deploy, prop);
if (match != null) {
@@ -164,7 +136,6 @@ public class DeployBeanPropertyLists {
}
private DeployBeanProperty findImported(DeployBeanDescriptor> deploy, DeployBeanProperty embeddedScalar) {
-
// the logical name and db column we are looking for a match on
String name = embeddedScalar.getName();
String dbColumn = embeddedScalar.getDbColumn();
@@ -180,7 +151,6 @@ public class DeployBeanPropertyLists {
return assocOne;
}
}
-
return null;
}
@@ -368,7 +338,6 @@ public class DeployBeanPropertyLists {
}
public BeanProperty getSoftDeleteProperty() {
-
for (BeanProperty prop : nonManys) {
if (prop.isSoftDelete()) {
return prop;
@@ -385,7 +354,6 @@ public class DeployBeanPropertyLists {
* Return the properties set via generated values on insert.
*/
public BeanProperty[] getGeneratedInsert() {
-
List list = new ArrayList<>();
for (BeanProperty prop : nonTransients) {
GeneratedProperty gen = prop.getGeneratedProperty();
@@ -400,7 +368,6 @@ public class DeployBeanPropertyLists {
* Return the properties set via generated values on update.
*/
public BeanProperty[] getGeneratedUpdate() {
-
List list = new ArrayList<>();
for (BeanProperty prop : nonTransients) {
GeneratedProperty gen = prop.getGeneratedProperty();
@@ -438,8 +405,7 @@ public class DeployBeanPropertyLists {
}
}
}
-
- return (BeanPropertyAssocOne[]) list.toArray(new BeanPropertyAssocOne[0]);
+ return list.toArray(new BeanPropertyAssocOne>[0]);
}
private BeanPropertyAssocMany>[] getMany2Many() {
@@ -449,8 +415,7 @@ public class DeployBeanPropertyLists {
list.add(prop);
}
}
-
- return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]);
+ return list.toArray(new BeanPropertyAssocMany>[0]);
}
private BeanPropertyAssocMany>[] getMany(Mode mode) {
@@ -471,25 +436,23 @@ public class DeployBeanPropertyLists {
break;
}
}
-
- return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]);
+ return list.toArray(new BeanPropertyAssocMany>[0]);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private BeanProperty createBeanProperty(BeanDescriptorMap owner, DeployBeanProperty deployProp) {
-
if (deployProp instanceof DeployBeanPropertyAssocOne) {
return new BeanPropertyAssocOne(owner, desc, (DeployBeanPropertyAssocOne) deployProp);
}
-
if (deployProp instanceof DeployBeanPropertySimpleCollection>) {
return new BeanPropertySimpleCollection(desc, (DeployBeanPropertySimpleCollection) deployProp);
}
-
if (deployProp instanceof DeployBeanPropertyAssocMany) {
return new BeanPropertyAssocMany(desc, (DeployBeanPropertyAssocMany) deployProp);
}
-
+ if (deployProp.isJsonMapper()) {
+ return new BeanPropertyJsonMapper(desc, deployProp);
+ }
return new BeanProperty(desc, deployProp);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
index 65c7cf45e..0792c66d5 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java
@@ -1,10 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
-import io.ebean.annotation.DbArray;
-import io.ebean.annotation.DbJson;
-import io.ebean.annotation.DbJsonB;
-import io.ebean.annotation.DbJsonType;
-import io.ebean.annotation.DbMap;
+import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.EncryptDeploy;
import io.ebean.config.EncryptDeployManager;
@@ -213,22 +209,21 @@ public class DeployUtil {
* This property is marked as a Lob object.
*/
void setDbJsonType(DeployBeanProperty prop, DbJson dbJsonType) {
-
int dbType = getDbJsonStorage(dbJsonType.storage());
- setDbJsonType(prop, dbType, dbJsonType.length());
+ setDbJsonType(prop, dbType, dbJsonType.length(), dbJsonType.mutationDetection());
}
void setDbJsonBType(DeployBeanProperty prop, DbJsonB dbJsonB) {
- setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length());
+ setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length(), dbJsonB.mutationDetection());
}
- private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
-
+ private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength, MutationDetection mutationDetection) {
+ prop.setDbType(dbType);
+ prop.setMutationDetection(mutationDetection);
ScalarType> scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength);
if (scalarType == null) {
throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]");
}
- prop.setDbType(dbType);
prop.setScalarType(scalarType);
if (dbType == Types.VARCHAR || dbLength > 0) {
// determine the db column size
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
index 189613808..0a4bb6b8a 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java
@@ -63,6 +63,11 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
}
}
+ @Override
+ public void pushJson(String json) {
+ dataBind.pushJson(json);
+ }
+
@Override
public long now() {
return now;
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java
new file mode 100644
index 000000000..1cbb7eeca
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonInsert.java
@@ -0,0 +1,42 @@
+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;
+
+/**
+ * For JSON Jackson properties - dirty detection via MD5 of json content.
+ */
+class BindablePropertyJsonInsert extends BindableProperty {
+
+ private final int propertyIndex;
+
+ BindablePropertyJsonInsert(BeanProperty prop) {
+ super(prop);
+ this.propertyIndex = prop.getPropertyIndex();
+ }
+
+ /**
+ * Normal binding of a property value from the bean.
+ */
+ @Override
+ public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
+ if (bean == null) {
+ request.bind(null, prop);
+ } else {
+ Object value = prop.getValue(bean);
+ if (value == null) {
+ request.bind(null, prop);
+ } else {
+ // on insert store hash and push json
+ final String json = prop.format(value);
+ final MutableValueInfo hash = prop.createMutableInfo(json);
+ bean._ebean_getIntercept().mutableInfo(propertyIndex, hash);
+ request.pushJson(json);
+ request.bind(value, prop);
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000..cfa056a63
--- /dev/null
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindablePropertyJsonUpdate.java
@@ -0,0 +1,33 @@
+package io.ebeaninternal.server.persist.dmlbind;
+
+import io.ebean.bean.EntityBean;
+import io.ebeaninternal.server.deploy.BeanProperty;
+
+import java.sql.SQLException;
+
+/**
+ * For JSON Jackson properties - dirty detection via MD5 of json content.
+ */
+class BindablePropertyJsonUpdate extends BindableProperty {
+
+ private final int propertyIndex;
+
+ BindablePropertyJsonUpdate(BeanProperty prop) {
+ super(prop);
+ this.propertyIndex = prop.getPropertyIndex();
+ }
+
+ /**
+ * Normal binding of a property value from the bean.
+ */
+ @Override
+ public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
+ if (bean == null) {
+ request.bind(null, prop);
+ } else {
+ // update mutableInfo and push json
+ request.pushJson(bean._ebean_getIntercept().mutableNext(propertyIndex));
+ request.bind(prop.getValue(bean), prop);
+ }
+ }
+}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java
index 4a1fc6754..e06862e86 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/BindableRequest.java
@@ -57,4 +57,9 @@ public interface BindableRequest {
* Return true if this is an update request.
*/
boolean isUpdate();
+
+ /**
+ * Push json content for scalarType bind().
+ */
+ void pushJson(String json);
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java
index b47b4ecb6..d5c59daf3 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dmlbind/FactoryProperty.java
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.persist.dmlbind;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
+import io.ebeaninternal.server.deploy.BeanPropertyJsonMapper;
import io.ebeaninternal.server.persist.dml.DmlMode;
/**
@@ -23,14 +24,12 @@ class FactoryProperty {
* Create a Bindable for the property given the mode and withLobs flag.
*/
public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs, boolean allowManyToOne) {
-
if (DmlMode.INSERT == mode && !prop.isDbInsertable()) {
return null;
}
if (DmlMode.UPDATE == mode && !prop.isDbUpdatable()) {
return null;
}
-
if (prop.isLob() && !withLobs) {
// Lob exclusion
return null;
@@ -38,11 +37,16 @@ class FactoryProperty {
if (prop.isDbEncrypted()){
return new BindableEncryptedProperty(prop, bindEncryptDataFirst);
}
-
if (allowManyToOne && prop instanceof BeanPropertyAssocOne) {
return new BindableAssocOne((BeanPropertyAssocOne>)prop);
}
-
+ if (prop instanceof BeanPropertyJsonMapper) {
+ if (DmlMode.INSERT == mode) {
+ return new BindablePropertyJsonInsert(prop);
+ } else if (DmlMode.UPDATE == mode) {
+ return new BindablePropertyJsonUpdate(prop);
+ }
+ }
return new BindableProperty(prop);
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java
index ac1386e11..83c7b2936 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlBeanLoad.java
@@ -18,19 +18,15 @@ public class SqlBeanLoad {
private final DbReadContext ctx;
private final EntityBean bean;
private final EntityBeanIntercept ebi;
-
private final Class> type;
private final boolean lazyLoading;
- private final boolean refreshLoading;
private final boolean rawSql;
SqlBeanLoad(DbReadContext ctx, Class> type, EntityBean bean, Mode queryMode) {
-
this.ctx = ctx;
this.rawSql = ctx.isRawSql();
this.type = type;
this.lazyLoading = queryMode == Mode.LAZYLOAD_BEAN;
- this.refreshLoading = queryMode == Mode.REFRESH_BEAN;
this.bean = bean;
this.ebi = bean == null ? null : bean._ebean_getIntercept();
}
@@ -50,34 +46,21 @@ public class SqlBeanLoad {
}
public Object load(BeanProperty prop) {
-
if (!rawSql && !prop.isLoadProperty(ctx.isDraftQuery())) {
return null;
}
-
if ((bean == null)
|| (lazyLoading && ebi.isLoadedProperty(prop.getPropertyIndex()))
|| (type != null && !prop.isAssignableFrom(type))) {
-
// ignore this property
// ... null: bean already in persistence context
// ... lazyLoading: partial bean that is lazy loading
// ... type: inheritance and not assignable to this instance
-
prop.loadIgnore(ctx);
return null;
}
-
try {
- Object dbVal = prop.read(ctx);
- if (!refreshLoading) {
- prop.setValue(bean, dbVal);
- } else {
- prop.setValueIntercept(bean, dbVal);
- }
-
- return dbVal;
-
+ return prop.readSet(ctx, bean);
} catch (Exception e) {
bean._ebean_getIntercept().setLoadError(prop.getPropertyIndex(), e);
ctx.handleLoadError(prop.getFullBeanName(), e);
@@ -89,10 +72,6 @@ public class SqlBeanLoad {
* Load the given value into the property.
*/
public void load(BeanProperty target, Object dbVal) {
- if (!refreshLoading) {
- target.setValue(bean, dbVal);
- } else {
- target.setValueIntercept(bean, dbVal);
- }
+ target.setValue(bean, dbVal);
}
}
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java
index 2959c872c..a1a9ec8cf 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DataBind.java
@@ -36,6 +36,7 @@ public class DataBind implements DataBinder {
private List inputStreams;
protected int pos;
+ private String json;
public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) {
this.dataTimeZone = dataTimeZone;
@@ -43,6 +44,19 @@ public class DataBind implements DataBinder {
this.connection = connection;
}
+ @Override
+ public void pushJson(String json) {
+ assert this.json == null; // we can only push one value
+ this.json = json;
+ }
+
+ @Override
+ public String popJson() {
+ String ret = json;
+ json = null;
+ return ret;
+ }
+
@Override
public StringBuilder append(Object entry) {
return bindLog.append(entry);
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
index befe46b20..28999e838 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.JsonConfig;
@@ -426,7 +425,7 @@ public final class DefaultTypeManager implements TypeManager {
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
- return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType);
+ return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, prop, dbType, docType);
}
/**
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java
index 9763a5667..b85c8e688 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java
@@ -20,22 +20,29 @@ import java.util.Calendar;
public class RsetDataReader implements DataReader {
private static final int bufferSize = 512;
-
static final int clobBufferSize = 512;
-
static final int stringInitialSize = 512;
private final DataTimeZone dataTimeZone;
-
private final ResultSet rset;
-
protected int pos;
+ private String json;
public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) {
this.dataTimeZone = dataTimeZone;
this.rset = rset;
}
+ @Override
+ public void pushJson(String json) {
+ this.json = json;
+ }
+
+ @Override
+ public String popJson() {
+ return json;
+ }
+
@Override
public void close() throws SQLException {
rset.close();
diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
index 5e24412b5..7fb3aa4b4 100644
--- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
+++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
+import io.ebean.annotation.MutationDetection;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
@@ -15,6 +16,7 @@ import io.ebean.text.TextException;
import io.ebeaninternal.json.ModifyAwareList;
import io.ebeaninternal.json.ModifyAwareMap;
import io.ebeaninternal.json.ModifyAwareSet;
+import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.io.DataInput;
@@ -34,8 +36,16 @@ class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
*/
- static ScalarType> createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
+ static ScalarType> createTypeFor(TypeJsonManager jsonManager, DeployBeanProperty prop, int dbType, DocPropertyType docType) {
+ AnnotatedField field = (AnnotatedField) prop.getJacksonField();
Class> type = field.getRawType();
+
+ MutationDetection mode = prop.getMutationDetection();
+ if (mode == MutationDetection.NONE) {
+ return new NoMutationDetection(jsonManager, field, dbType, type);
+ } else if (mode != MutationDetection.DEFAULT) {
+ return new GenericObject(jsonManager, field, dbType, type);
+ }
if (Set.class.equals(type)) {
return new OmSet(jsonManager, field, dbType, docType);
}
@@ -45,17 +55,77 @@ class ScalarTypeJsonObjectMapper {
if (Map.class.equals(type)) {
return new OmMap(jsonManager, field, dbType);
}
+ prop.setMutationDetection(MutationDetection.HASH);
return new GenericObject(jsonManager, field, dbType, type);
}
/**
- * Maps any type (Object) using Jackson ObjectMapper.
+ * No mutation detection on this json property.
+ */
+ private static class NoMutationDetection extends Base