From 20becf951d02754f4003cb88a1b5aa1ea522de3c Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 29 Jul 2021 23:11:42 +1200 Subject: [PATCH] Use MutationDetection replacing dirtyDetection and keepSource Also adds NoMutationDetection to support NONE --- ebean-api/pom.xml | 2 +- .../server/deploy/BeanPropertyJsonMapper.java | 46 ++++++------------- .../deploy/meta/DeployBeanProperty.java | 39 ++++------------ .../deploy/meta/DeployBeanPropertyLists.java | 42 ++--------------- .../server/deploy/parse/DeployUtil.java | 16 +++---- .../server/type/DefaultTypeManager.java | 3 +- .../type/ScalarTypeJsonObjectMapper.java | 35 +++++++++++++- .../org/tests/model/json/EBasicJsonList.java | 11 ++--- .../org/tests/model/json/EBasicPlain.java | 4 +- .../model/json/TestJacksonPlainBean.java | 5 +- 10 files changed, 76 insertions(+), 127 deletions(-) diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index 649f85822..503c2f2c5 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -50,7 +50,7 @@ io.ebean ebean-annotation - 7.1 + 7.2 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 d3a683e48..f1efc39f7 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 @@ -1,9 +1,10 @@ package io.ebeaninternal.server.deploy; +import io.ebean.annotation.MutationDetection; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; -import io.ebean.bean.MutableValueNext; 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; @@ -14,26 +15,24 @@ 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 static final NoDirtyDetection NO_DIRTY_DETECTION = new NoDirtyDetection(); - private final boolean dirtyDetection; - private final boolean keepSource; + private final boolean sourceDetection; public BeanPropertyJsonMapper(BeanDescriptor desc, DeployBeanProperty deployProp) { super(desc, deployProp); - this.dirtyDetection = deployProp.isDirtyDetection(); - this.keepSource = deployProp.isKeepSource(); + this.sourceDetection = deployProp.getMutationDetection() == MutationDetection.SOURCE; } @Override public MutableValueInfo createMutableInfo(String json) { - if (keepSource) { + if (sourceDetection) { return new SourceMutableValue(scalarType, json); - } else if (dirtyDetection) { - return new ChecksumMutableValue(scalarType, json); } else { - return NO_DIRTY_DETECTION; + return new ChecksumMutableValue(scalarType, json); } } @@ -41,22 +40,19 @@ public class BeanPropertyJsonMapper extends BeanProperty { * Next when no prior MutableValueInfo. */ private MutableValueNext next(String json) { - if (keepSource) { + if (sourceDetection) { return new SourceMutableValue(scalarType, json); - } else if (dirtyDetection) { - return new NextPair(json, new ChecksumMutableValue(scalarType, json)); } else { - throw new IllegalStateException("Never get here"); + return new NextPair(json, new ChecksumMutableValue(scalarType, json)); } } /** - * Return true if the mutable value is considered dirty. - * This is only used for 'mutable' scalar types like hstore etc. + * Return true if the json property is considered dirty. */ @Override boolean isDirtyValue(Object value, EntityBeanIntercept ebi) { - // dirty detection based on json content or checksum of json content + // 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); @@ -193,20 +189,4 @@ public class BeanPropertyJsonMapper extends BeanProperty { return this; } } - - /** - * No dirty detection on json content. - */ - private static final class NoDirtyDetection implements MutableValueInfo { - - @Override - public MutableValueNext nextDirty(String json) { - return null; // treat as not dirty - } - - @Override - public boolean isEqualToObject(Object obj) { - return true; // treat as not dirty - } - } } 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 060ce802f..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,8 +96,7 @@ public class DeployBeanProperty { private boolean jsonSerialize = true; private boolean jsonDeserialize = true; - private boolean dirtyDetection; - private boolean keepSource; + private MutationDetection mutationDetection; private boolean dbEncrypted; private DbEncryptFunction dbEncryptFunction; @@ -329,18 +315,15 @@ public class DeployBeanProperty { this.jsonDeserialize = jsonDeserialize; } - /** - * Return true if we should have JSON dirty detection on this property. - */ - public boolean isDirtyDetection() { - return dirtyDetection; + public MutationDetection getMutationDetection() { + if (mutationDetection == null) { + mutationDetection = MutationDetection.DEFAULT; + } + return mutationDetection; } - /** - * Return true if we should store source JSON content on this property. - */ - public boolean isKeepSource() { - return keepSource; + public void setMutationDetection(MutationDetection dirtyDetection) { + this.mutationDetection = dirtyDetection; } /** @@ -1221,8 +1204,4 @@ public class DeployBeanProperty { return scalarType != null && scalarType.isJsonMapper(); } - public void setJsonOptions(boolean dirtyDetection, boolean keepSource) { - this.dirtyDetection = dirtyDetection; - this.keepSource = keepSource; - } } 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 9843f8e5c..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 @@ -22,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; @@ -78,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<>(); @@ -119,7 +100,7 @@ public class DeployBeanPropertyLists { } if (orderColumn != null) { - orderColumn.setDeployOrder(order++); + orderColumn.setDeployOrder(order); allocateToList(orderColumn); propertyMap.put(orderColumn.getName(), orderColumn); } @@ -146,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) { @@ -156,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(); @@ -172,7 +151,6 @@ public class DeployBeanPropertyLists { return assocOne; } } - return null; } @@ -360,7 +338,6 @@ public class DeployBeanPropertyLists { } public BeanProperty getSoftDeleteProperty() { - for (BeanProperty prop : nonManys) { if (prop.isSoftDelete()) { return prop; @@ -377,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(); @@ -392,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(); @@ -430,8 +405,7 @@ public class DeployBeanPropertyLists { } } } - - return (BeanPropertyAssocOne[]) list.toArray(new BeanPropertyAssocOne[0]); + return list.toArray(new BeanPropertyAssocOne[0]); } private BeanPropertyAssocMany[] getMany2Many() { @@ -441,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) { @@ -463,25 +436,20 @@ 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); } 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 2245b0c4c..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; @@ -214,21 +210,21 @@ public class DeployUtil { */ void setDbJsonType(DeployBeanProperty prop, DbJson dbJsonType) { int dbType = getDbJsonStorage(dbJsonType.storage()); - setDbJsonType(prop, dbType, dbJsonType.length(), dbJsonType.dirtyDetection(), dbJsonType.keepSource()); + setDbJsonType(prop, dbType, dbJsonType.length(), dbJsonType.mutationDetection()); } void setDbJsonBType(DeployBeanProperty prop, DbJsonB dbJsonB) { - setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length(), dbJsonB.dirtyDetection(), dbJsonB.keepSource()); + setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length(), dbJsonB.mutationDetection()); } - private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength, boolean dirtyDetection, boolean keepSource) { + 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); - prop.setJsonOptions(dirtyDetection, keepSource); if (dbType == Types.VARCHAR || dbLength > 0) { // determine the db column size int columnLength = (dbLength > 0) ? dbLength : DEFAULT_JSON_VARCHAR_LENGTH; 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/ScalarTypeJsonObjectMapper.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java index 14425c6c9..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,11 +55,32 @@ 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 { + + NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class rawType) { + super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType); + } + + @Override + public boolean isMutable() { + return false; + } + + @Override + public boolean isDirty(Object value) { + return false; + } + } + + /** + * Supports HASH and SOURCE dirty detection modes. */ private static class GenericObject extends Base { diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java index 05d9807b5..d5ce2c309 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicJsonList.java @@ -7,12 +7,9 @@ import io.ebean.annotation.DbJsonType; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; + +import static io.ebean.annotation.MutationDetection.SOURCE; @Entity public class EBasicJsonList { @@ -31,7 +28,7 @@ public class EBasicJsonList { @DbJson(length = 700) Map beanMap = new LinkedHashMap<>(); - @DbJson(length = 500, keepSource = true) // such that we can rebuild old values + @DbJson(length = 500, mutationDetection = SOURCE) // such that we can rebuild old values PlainBean plainBean; @DbJson(length = 50) diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java b/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java index 401e66fa1..051b4a344 100644 --- a/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicPlain.java @@ -6,6 +6,8 @@ import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Version; +import static io.ebean.annotation.MutationDetection.NONE; + @Entity public class EBasicPlain { @@ -17,7 +19,7 @@ public class EBasicPlain { @DbJson(length = 500) PlainBean plainBean; - @DbJson(length = 500, dirtyDetection = false) + @DbJson(length = 500, mutationDetection = NONE) // only update when property set PlainBean plainBean2; @Version diff --git a/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java b/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java index f77f2f8b6..c0fe82b26 100644 --- a/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java +++ b/ebean-core/src/test/java/org/tests/model/json/TestJacksonPlainBean.java @@ -60,7 +60,7 @@ public class TestJacksonPlainBean { // dirtyDetection = false, set a new plainBean2 instance, included in update found.setPlainBean2(new PlainBean("bar", 27)); DB.save(found); - expectedSql( 0, "update ebasic_plain set plain_bean2=?, version=? where id=? and version=?"); + expectedSql(0, "update ebasic_plain set plain_bean2=?, version=? where id=? and version=?"); LoggedSqlCollector.stop(); } @@ -69,7 +69,4 @@ public class TestJacksonPlainBean { assertThat(LoggedSqlCollector.current().get(i)).contains(s); } - private void expectedSql(List sql, int i, String s) { - assertThat(sql.get(i)).contains(s); - } }