From c5b92d5171399a0c5035ad82c0f70a330d7b8718 Mon Sep 17 00:00:00 2001 From: awake Date: Mon, 24 Jun 2024 14:56:09 +0800 Subject: [PATCH 1/2] perf[orm]: Rewrite collection declaration type check & Support deserialization into thread safe classes --- .../java/com/zfoo/orm/codec/MapCodec.java | 14 +++++- .../java/com/zfoo/orm/manager/OrmManager.java | 49 +++++++++++-------- .../java/com/zfoo/orm/accessor/MapTest.java | 18 +++++++ .../com/zfoo/orm/entity/bag/MapEntity.java | 34 ++++++++++++- 4 files changed, 92 insertions(+), 23 deletions(-) diff --git a/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java b/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java index 533d440d..ab0b2161 100644 --- a/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java +++ b/orm/src/main/java/com/zfoo/orm/codec/MapCodec.java @@ -17,6 +17,7 @@ import org.bson.BsonWriter; import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; +import org.bson.codecs.configuration.CodecConfigurationException; import java.util.HashMap; import java.util.Map; @@ -61,7 +62,7 @@ public class MapCodec implements Codec> { @Override public Map decode(BsonReader reader, DecoderContext context) { - var map = new HashMap(); + var map = getInstance(); reader.readStartDocument(); while (BsonType.END_OF_DOCUMENT != reader.readBsonType()) { K key = keyDecodeFunction.apply(reader.readName()); @@ -81,6 +82,15 @@ public class MapCodec implements Codec> { public Class> getEncoderClass() { return encoderClass; } - + private Map getInstance() { + if (encoderClass.isInterface()) { + return new HashMap<>(); + } + try { + return encoderClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new CodecConfigurationException(e.getMessage(), e); + } + } } diff --git a/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java index 3622b7a2..29efd7aa 100644 --- a/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java +++ b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java @@ -40,6 +40,8 @@ import org.bson.Document; import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.pojo.PojoCodecProvider; import org.bson.types.ObjectId; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; @@ -61,7 +63,7 @@ import java.util.concurrent.TimeUnit; * @author godotg */ public class OrmManager implements IOrmManager { - + private static final Logger logger = LoggerFactory.getLogger(OrmManager.class); private OrmConfig ormConfig; private MongoClient mongoClient; @@ -479,6 +481,7 @@ public class OrmManager implements IOrmManager { } var filedList = ReflectionUtils.notStaticAndTransientFields(clazz); + boolean concurrentSecurityFlag=true; for (var field : filedList) { // entity必须包含属性的get和set方法 @@ -495,9 +498,10 @@ public class OrmManager implements IOrmManager { // ORM的数组类型只支持byte[] AssertionUtils.isTrue(arrayClazz == byte.class, "The array type of ORM[class:{}] only supports byte[]", clazz.getCanonicalName()); } else if (Set.class.isAssignableFrom(fieldType)) { - // 必须是Set接口类型 - AssertionUtils.isTrue(fieldType.equals(Set.class), "[class:{}] type declaration is incorrect, and it must be of the Set interface type", clazz.getCanonicalName()); - + // 是一个Set + if (fieldType.equals(Set.class)){ + concurrentSecurityFlag = false; + } var type = field.getGenericType(); // field必须泛型类 AssertionUtils.isTrue(type instanceof ParameterizedType, "[class:{}] type declaration is incorrect, not a generic class[field:{}]", clazz.getCanonicalName(), field.getName()); @@ -508,8 +512,9 @@ public class OrmManager implements IOrmManager { checkSubEntity(clazz, types[0]); } else if (List.class.isAssignableFrom(fieldType)) { // 是一个List - AssertionUtils.isTrue(fieldType.equals(List.class), "[class:{}] type declaration is incorrect, and it must be of the List interface type", clazz.getCanonicalName()); - + if (fieldType.equals(List.class)){ + concurrentSecurityFlag = false; + } var type = field.getGenericType(); // field必须泛型类 AssertionUtils.isTrue(type instanceof ParameterizedType, "[class:{}] type declaration is incorrect, not a generic class[field:{}]", clazz.getCanonicalName(), field.getName()); @@ -520,9 +525,9 @@ public class OrmManager implements IOrmManager { checkSubEntity(clazz, types[0]); } else if (Map.class.isAssignableFrom(fieldType)) { - // 必须是Map接口类型 - if (!fieldType.equals(Map.class)) { - throw new RunException("[class:{}] type declaration is incorrect, and it must be a Map interface type", clazz.getCanonicalName()); + // 是Map接口类型 + if (fieldType.equals(Map.class)) { + concurrentSecurityFlag=false; } var type = field.getGenericType(); @@ -543,30 +548,34 @@ public class OrmManager implements IOrmManager { if (!ClassUtils.isBaseType((Class) keyType)) { throw new RunException("[class:{}] type declaration is incorrect, and the key type of the Map must be the Base type", clazz.getCanonicalName()); } - - checkSubEntity(clazz, valueType); + if (concurrentSecurityFlag) { + concurrentSecurityFlag = checkSubEntity(clazz, valueType); + } } else if (ObjectId.class.isAssignableFrom(fieldType)) { // do nothing } else { checkEntity(fieldType); } } + if (!concurrentSecurityFlag) { + logger.warn("class[{}] has collection not declared with specified implementation, deserialization defaults to using the thread unsafe implementation", clazz.getSimpleName()); + } } - private void checkSubEntity(Class currentEntityClass, Type type) { + private boolean checkSubEntity(Class currentEntityClass, Type type) { if (type instanceof ParameterizedType) { // 泛型类 Class clazz = (Class) ((ParameterizedType) type).getRawType(); - if (Set.class.equals(clazz)) { + if (Set.class.isAssignableFrom(clazz)) { // Set> checkSubEntity(currentEntityClass, ((ParameterizedType) type).getActualTypeArguments()[0]); - return; - } else if (List.class.equals(clazz)) { + return clazz.equals(Set.class); + } else if (List.class.isAssignableFrom(clazz)) { // List> checkSubEntity(currentEntityClass, ((ParameterizedType) type).getActualTypeArguments()[0]); - return; - } else if (Map.class.equals(clazz)) { + return clazz.equals(List.class); + } else if (Map.class.isAssignableFrom(clazz)) { // Map, List> var types = ((ParameterizedType) type).getActualTypeArguments(); var keyType = types[0]; @@ -575,13 +584,13 @@ public class OrmManager implements IOrmManager { throw new RunException("The key of the map in the ORM must be of the Base type"); } checkSubEntity(currentEntityClass, valueType); - return; + return clazz.equals(Map.class); } } else if (type instanceof Class) { Class clazz = ((Class) type); if (isBaseType(clazz)) { // do nothing - return; + return true; } else if (clazz.getComponentType() != null) { // ORM不支持多维数组或集合嵌套数组类型,仅支持一维数组 throw new RunException("[type:{}] does not support multi-dimensional arrays or nested arrays, and only supports one-dimensional arrays", type); @@ -590,7 +599,7 @@ public class OrmManager implements IOrmManager { throw new RunException("ORMs do not support the combination of arrays and collections with the [type:{}] type", type); } else { checkEntity(clazz); - return; + return true; } } throw new RunException("[type:{}] is incorrect", type); diff --git a/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java b/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java index db32cac3..3265df7b 100644 --- a/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java +++ b/orm/src/test/java/com/zfoo/orm/accessor/MapTest.java @@ -27,6 +27,8 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; @Ignore public class MapTest { @@ -54,6 +56,22 @@ public class MapTest { entity.setList(List.of(1, 2, 3)); + CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList<>(); + copyOnWriteArrayList.add(1); + copyOnWriteArrayList.add(2); + entity.setCopyOnWriteArrayList(copyOnWriteArrayList); + + ConcurrentHashMap concurrentHashMap=new ConcurrentHashMap<>(); + concurrentHashMap.put(1L,1); + concurrentHashMap.put(2L,2); + entity.setConcurrentHashMap(concurrentHashMap); + + ConcurrentHashMap> concurrentHashMapConcurrentHashMap=new ConcurrentHashMap<>(); + + ConcurrentHashMap conMap = new ConcurrentHashMap<>(); + conMap.put(1,1); + concurrentHashMapConcurrentHashMap.putIfAbsent(1L,conMap); + entity.setConcurrentHashMapAndConcurrentHashMap(concurrentHashMapConcurrentHashMap); var bagMap = new HashMap(); entity.setBagMap(bagMap); diff --git a/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java b/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java index 5175e7c7..3aad90a4 100644 --- a/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java +++ b/orm/src/test/java/com/zfoo/orm/entity/bag/MapEntity.java @@ -19,6 +19,7 @@ import com.zfoo.orm.anno.Persister; import com.zfoo.orm.model.IEntity; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @EntityCache(persister = @Persister("time30s")) @@ -26,7 +27,13 @@ public class MapEntity implements IEntity { @Id private long id; - private List list = new CopyOnWriteArrayList<>(); + private List list = new ArrayList<>(); + + private CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList<>(); + + private ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<>(); + + private ConcurrentHashMap> concurrentHashMapAndConcurrentHashMap = new ConcurrentHashMap<>(); private Map bagMap = new HashMap<>(); private Map> baseMap = new HashMap<>(); @@ -168,6 +175,31 @@ public class MapEntity implements IEntity { this.doubleBagMap = doubleBagMap; } + public CopyOnWriteArrayList getCopyOnWriteArrayList() { + return copyOnWriteArrayList; + } + + public void setCopyOnWriteArrayList(CopyOnWriteArrayList copyOnWriteArrayList) { + this.copyOnWriteArrayList = copyOnWriteArrayList; + } + + public ConcurrentHashMap getConcurrentHashMap() { + return concurrentHashMap; + } + + public void setConcurrentHashMap(ConcurrentHashMap concurrentHashMap) { + this.concurrentHashMap = concurrentHashMap; + } + + + public ConcurrentHashMap> getConcurrentHashMapAndConcurrentHashMap() { + return concurrentHashMapAndConcurrentHashMap; + } + + public void setConcurrentHashMapAndConcurrentHashMap(ConcurrentHashMap> concurrentHashMapAndConcurrentHashMap) { + this.concurrentHashMapAndConcurrentHashMap = concurrentHashMapAndConcurrentHashMap; + } + @Override public boolean equals(Object o) { if (this == o) return true; From 0ca1c9d78f85a3dbf8f179e325cccef1fa5c948b Mon Sep 17 00:00:00 2001 From: awake Date: Mon, 24 Jun 2024 15:07:35 +0800 Subject: [PATCH 2/2] perf[orm]: fix private id --- orm/src/main/java/com/zfoo/orm/model/EntityDef.java | 1 + 1 file changed, 1 insertion(+) diff --git a/orm/src/main/java/com/zfoo/orm/model/EntityDef.java b/orm/src/main/java/com/zfoo/orm/model/EntityDef.java index ec646418..bdf97bb3 100644 --- a/orm/src/main/java/com/zfoo/orm/model/EntityDef.java +++ b/orm/src/main/java/com/zfoo/orm/model/EntityDef.java @@ -63,6 +63,7 @@ public class EntityDef { public IEntity newEntity(Object id) { var entity = ReflectionUtils.newInstance(clazz); var idFields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class); + ReflectionUtils.makeAccessible(idFields[0]); ReflectionUtils.setField(idFields[0], entity, id); return entity; }