Bladeren bron

优化逻辑主键策略以及单元测试

zhouhao 6 jaren geleden
bovenliggende
commit
a8353f8afb

+ 1 - 0
hsweb-commons/hsweb-commons-entity/src/main/java/org/hswebframework/web/commons/entity/param/QueryParamEntity.java

@@ -43,4 +43,5 @@ public class QueryParamEntity extends QueryParam implements QueryEntity {
     public static QueryParamEntity single(String field, Object value) {
         return empty().where(field, value);
     }
+
 }

+ 94 - 51
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/DefaultLogicPrimaryKeyValidator.java

@@ -8,7 +8,6 @@ import org.hswebframework.ezorm.core.dsl.Query;
 import org.hswebframework.ezorm.core.param.TermType;
 import org.hswebframework.web.bean.FastBeanCopier;
 import org.hswebframework.web.commons.entity.param.QueryParamEntity;
-import org.hswebframework.web.validator.DuplicateKeyException;
 import org.hswebframework.web.validator.LogicPrimaryKey;
 import org.hswebframework.web.validator.LogicPrimaryKeyValidator;
 import org.springframework.core.annotation.AnnotationUtils;
@@ -19,6 +18,7 @@ import org.springframework.util.StringUtils;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @SuppressWarnings("all")
 @Slf4j
@@ -28,9 +28,10 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
 
 
     private static final Validator EMPTY_VALIDATOR = bean -> {
+        return Result.passed();
     };
 
-    public <T> void registerQuerySuppiler(Class type, Function<T, Query<T, QueryParamEntity>> querySupplier) {
+    public <T> void registerQuerySuppiler(Class<T> type, Function<T, Query<T, QueryParamEntity>> querySupplier) {
         validatorCache.computeIfAbsent(type, this::createValidator)
                 .values()
                 .stream()
@@ -40,20 +41,26 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
     }
 
     @Override
-    public void validate(Object bean, Class... groups) {
+    public Result validate(Object bean, Class... groups) {
 
         Class target = ClassUtils.getUserClass(bean);
+        Result result;
         if (null != groups && groups.length > 0) {
-            for (Class group : groups) {
-                validatorCache.computeIfAbsent(target, this::createValidator)
-                        .getOrDefault(group, EMPTY_VALIDATOR)
-                        .doValidate(bean);
-            }
+            result = Arrays.stream(groups)
+                    .map(group ->
+                            validatorCache.computeIfAbsent(target, this::createValidator)
+                                    .getOrDefault(group, EMPTY_VALIDATOR)
+                                    .doValidate(bean))
+                    .filter(Result::isError)
+                    .findFirst()
+                    .orElseGet(Result::passed);
+
         } else {
-            validatorCache.computeIfAbsent(target, this::createValidator)
+            result = validatorCache.computeIfAbsent(target, this::createValidator)
                     .getOrDefault(Void.class, EMPTY_VALIDATOR)
                     .doValidate(bean);
         }
+        return result;
 
     }
 
@@ -69,86 +76,103 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
         });
 
         //获取类上的注解
-        Class tempClass = target;
+        Class[] tempClass = new Class[]{target};
         LogicPrimaryKey classAnn = null;
+        Class[] empty = new Class[0];
 
-        while (classAnn == null) {
-            classAnn = AnnotationUtils.findAnnotation(tempClass, LogicPrimaryKey.class);
-            if (null != classAnn) {
-                if (classAnn.value().length > 0) {
-                    for (String field : classAnn.value()) {
-                        keys.put(field, classAnn);
+        while (tempClass.length != 0) {
+
+            for (Class group : tempClass) {
+                classAnn = AnnotationUtils.findAnnotation(group, LogicPrimaryKey.class);
+                if (null != classAnn) {
+                    if (classAnn.value().length > 0) {
+                        for (String field : classAnn.value()) {
+                            keys.put(field, classAnn);
+                        }
+                        tempClass = empty;
+                        continue;
+                    } else {
+                        //如果注解没有指定字段则从group中获取
+                        tempClass = classAnn.groups();
+                        if (tempClass.length == 1 && tempClass[0] == Void.class) {
+                            log.warn("类{}的注解{}无效,请设置value属性或者group属性", classAnn, tempClass);
+                            continue;
+                        }
                     }
-                    break;
                 } else {
-                    //如果注解没有指定字段则从group中获取
-                    tempClass = classAnn.group();
-                    if (tempClass == Void.class) {
-                        log.warn("类{}的注解{}无效,请设置value属性或者group属性", classAnn, tempClass);
-                        break;
-                    }
+                    tempClass = empty;
+                    continue;
                 }
-            } else {
-                break;
             }
+
         }
 
         if (keys.isEmpty()) {
             return Collections.emptyMap();
         }
+//
 
         return keys.entrySet()
                 .stream()
-                .collect(
-                        Collectors.groupingBy(
-
-                                //按注解中的group分组
-                                e -> e.getValue().group()
-                                //将分组后的注解转为字段配置
-                                , Collectors.collectingAndThen(
-                                        Collectors.mapping(e -> LogicPrimaryKeyField
-                                                .builder()
-                                                .field(e.getKey())
-                                                .termType(e.getValue().termType())
-                                                .condition(e.getValue().condition())
-                                                .matchNullOrEmpty(e.getValue().matchNullOrEmpty())
-                                                .build(), Collectors.toList())
-                                        //将每一组的字段集合构造为验证器对象
-                                        , list -> DefaultValidator
-                                                .builder()
-                                                .infos(list)
-                                                .build())
+                .flatMap(entry ->
+                        Stream.of(entry.getValue().groups())
+                                .flatMap(group ->
+                                        Stream.of(entry.getValue().value().length == 0 ? new String[]{entry.getKey()} : entry.getValue().value())
+                                                .map(field -> LogicPrimaryKeyField
+                                                        .builder()
+                                                        .field(field)
+                                                        .termType(entry.getValue().termType())
+                                                        .condition(entry.getValue().condition())
+                                                        .matchNullOrEmpty(entry.getValue().matchNullOrEmpty())
+                                                        .group(group)
+                                                        .build())
+                                ))
+                .collect(Collectors.groupingBy(
+                        //按group分组
+                        LogicPrimaryKeyField::getGroup
+                        //将每一组的集合构造为验证器对象
+                        , Collectors.collectingAndThen(
+                                Collectors.mapping(Function.identity(), Collectors.toSet())
+                                , list -> DefaultValidator
+                                        .builder()
+                                        .infos(list)
+                                        .build())
                         )
                 );
 
     }
 
     interface Validator<T> {
-        void doValidate(T bean);
+        Result doValidate(T bean);
     }
 
     @Builder
     static class DefaultValidator<T> implements Validator<T> {
-        private List<LogicPrimaryKeyField> infos = new ArrayList<>();
+        private Set<LogicPrimaryKeyField> infos = new HashSet<>();
 
         private volatile Function<T, Query<T, QueryParamEntity>> querySupplier;
 
-        public void doValidate(T bean) {
+        public Result doValidate(T bean) {
             if (querySupplier == null) {
-                return;
+                return Result.passed();
             }
 
             Query<T, QueryParamEntity> query = querySupplier.apply(bean);
 
+            //转为map
             Map<String, Object> mapBean = FastBeanCopier.copy(bean, new HashMap<>());
 
+            Map<String, Object> properties = new HashMap<>();
+
             for (LogicPrimaryKeyField info : infos) {
                 String field = info.getField();
                 Object value = mapBean.get(field);
+                //为空,可能是有字段嵌套,也有可能是真的null
                 if (value == null) {
                     String tmpField = field;
                     Object tmpValue = null;
                     Map<String, Object> tempMapBean = mapBean;
+                    //嵌套的场景
                     while (tmpValue == null && tmpField.contains(".")) {
                         String[] nest = tmpField.split("[.]", 2);
                         Object nestObject = tempMapBean.get(nest[0]);
@@ -165,7 +189,6 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
                     }
                     value = tmpValue;
                 }
-
                 if (StringUtils.isEmpty(value)) {
                     if (info.matchNullOrEmpty) {
                         if (value == null) {
@@ -178,14 +201,19 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
                     String termType = StringUtils.isEmpty(info.termType) ? TermType.eq : info.termType;
                     query.and(info.getField(), termType, value);
                 }
+                properties.put(info.getField(), value);
             }
 
             T result = query.single();
 
             if (result != null) {
-
-                throw new DuplicateKeyException(result, "存在重复数据");
+                Result validateResult = new Result();
+                validateResult.setError(true);
+                validateResult.setData(result);
+                validateResult.setProperties(properties);
+                return validateResult;
             }
+            return Result.passed();
         }
     }
 
@@ -200,5 +228,20 @@ public class DefaultLogicPrimaryKeyValidator implements LogicPrimaryKeyValidator
         private boolean matchNullOrEmpty;
 
         private String termType;
+
+        private Class group;
+
+        @Override
+        public int hashCode() {
+            return field.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof LogicPrimaryKeyField) {
+                return hashCode() == obj.hashCode();
+            }
+            return false;
+        }
     }
 }

+ 4 - 10
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/main/java/org/hswebframework/web/service/GenericEntityService.java

@@ -64,7 +64,7 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
     public void init() {
         if (null != logicPrimaryKeyValidator && logicPrimaryKeyValidator instanceof DefaultLogicPrimaryKeyValidator) {
             ((DefaultLogicPrimaryKeyValidator) logicPrimaryKeyValidator)
-                    .<E>registerQuerySuppiler(getEntityInstanceType(), bean -> this.createQuery().not("id", bean.getId()));
+                    .registerQuerySuppiler(getEntityInstanceType(), bean -> this.createQuery().not("id", bean.getId()));
         }
     }
 
@@ -113,15 +113,9 @@ public abstract class GenericEntityService<E extends GenericEntity<PK>, PK>
     @SuppressWarnings("unchecked")
     protected boolean dataExisted(E entity) {
         if (null != logicPrimaryKeyValidator) {
-            try {
-                logicPrimaryKeyValidator.validate(entity);
-            } catch (DuplicateKeyException e) {
-                if (getEntityType().isInstance(e.getData())) {
-                    PK id = ((E) e.getData()).getId();
-                    entity.setId(id);
-                    return true;
-                }
-            }
+            logicPrimaryKeyValidator
+                    .validate(entity)
+                    .ifError(result -> entity.setId(result.getData().getId()));
         }
         return null != entity.getId() && null != selectByPk(entity.getId());
     }

+ 145 - 0
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/test/java/org/hswebframework/web/service/DefaultLogicPrimaryKeyValidatorTest.java

@@ -0,0 +1,145 @@
+package org.hswebframework.web.service;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.hswebframework.ezorm.core.dsl.Query;
+import org.hswebframework.web.commons.entity.param.QueryParamEntity;
+import org.hswebframework.web.validator.LogicPrimaryKey;
+import org.hswebframework.web.validator.group.CreateGroup;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author zhouhao
+ * @since 3.0.0-RC
+ */
+public class DefaultLogicPrimaryKeyValidatorTest {
+    DefaultLogicPrimaryKeyValidator validator = new DefaultLogicPrimaryKeyValidator();
+
+    @Test
+    public void testSimple() {
+        validator.registerQuerySuppiler(TestBean.class, bean ->
+                Query.<TestBean, QueryParamEntity>empty(QueryParamEntity.empty())
+                        .setSingleExecutor(param -> {
+                            Assert.assertNotNull(param.getTerms());
+                            Assert.assertEquals(param.getTerms().size(), 2);
+                            return new TestBean("test", "1");
+                        }));
+
+        TestBean bean = new TestBean("test", "1");
+        Assert.assertTrue(validator.validate(bean).isError());
+    }
+
+
+    @Test
+    public void testClassAnn() {
+        validator.registerQuerySuppiler(ClassAnnTestBean.class, bean ->
+                Query.<ClassAnnTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
+                        .setSingleExecutor(param -> {
+                            Assert.assertNotNull(param.getTerms());
+                            Assert.assertEquals(param.getTerms().size(), 2);
+                            return new ClassAnnTestBean("test", "1");
+                        }));
+
+        ClassAnnTestBean bean = new ClassAnnTestBean("test", "1");
+        Assert.assertTrue(validator.validate(bean).isError());
+    }
+
+
+    @Test
+    public void testGroupAnn() {
+        validator.registerQuerySuppiler(GroupAnnTestBean.class, bean ->
+                Query.<GroupAnnTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
+                        .setSingleExecutor(param -> {
+                            Assert.assertNotNull(param.getTerms());
+                            Assert.assertEquals(param.getTerms().size(), 2);
+                            return new GroupAnnTestBean("test", "1");
+                        }));
+
+        GroupAnnTestBean bean = new GroupAnnTestBean("test", "1");
+        Assert.assertTrue(validator.validate(bean).isPassed());
+
+        Assert.assertTrue(validator.validate(bean, TestGroup.class).isError());
+    }
+
+
+    @Test
+    public void testNestProperty() {
+        NestTestBean nestTestBean=new NestTestBean(new TestBean("test","1"),"test");
+
+        validator.registerQuerySuppiler(NestTestBean.class, bean ->
+                Query.<NestTestBean, QueryParamEntity>empty(QueryParamEntity.empty())
+                        .setSingleExecutor(param -> {
+                            Assert.assertNotNull(param.getTerms());
+                            Assert.assertEquals(param.getTerms().size(), 2);
+                            return nestTestBean;
+                        }));
+
+        Assert.assertTrue(validator.validate(nestTestBean).isError());
+    }
+
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @LogicPrimaryKey(groups = TestGroup.class)
+    public class GroupAnnTestBean {
+
+        private String name;
+
+        private String org;
+
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @LogicPrimaryKey({"name", "org"})
+    public class ClassAnnTestBean {
+
+        private String name;
+
+        private String org;
+
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    public class GroupTestBean {
+
+        @LogicPrimaryKey(groups = CreateGroup.class)
+        private String name;
+
+        @LogicPrimaryKey
+        private String org;
+
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    public class NestTestBean {
+        @LogicPrimaryKey("nest.name")
+        private TestBean nest;
+
+        @LogicPrimaryKey
+        private String org;
+    }
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    public class TestBean {
+        @LogicPrimaryKey
+        private String name;
+
+        @LogicPrimaryKey
+        private String org;
+    }
+
+    @LogicPrimaryKey(value = {"name", "org"}, groups = TestGroup.class)
+    public interface TestGroup {
+
+    }
+}

+ 1 - 0
hsweb-commons/hsweb-commons-service/hsweb-commons-service-simple/src/test/java/org/hswebframework/web/service/TestEntity.java

@@ -4,6 +4,7 @@ import lombok.*;
 import org.hibernate.validator.constraints.NotBlank;
 import org.hswebframework.web.commons.entity.SimpleGenericEntity;
 import org.hswebframework.web.commons.entity.SimpleTreeSortSupportEntity;
+import org.hswebframework.web.validator.LogicPrimaryKey;
 import org.hswebframework.web.validator.group.CreateGroup;
 
 import java.util.List;

+ 8 - 3
hsweb-core/src/main/java/org/hswebframework/web/validator/DuplicateKeyException.java

@@ -10,10 +10,15 @@ import java.util.Map;
 @Setter
 public class DuplicateKeyException extends BusinessException {
 
-    private Object data;
+    private static final long serialVersionUID = 8449360528527473673L;
+    private LogicPrimaryKeyValidator.Result result;
 
-    public DuplicateKeyException(Object data, String message) {
+    public DuplicateKeyException(LogicPrimaryKeyValidator.Result result) {
+        this(result, result.getMessage());
+    }
+
+    public DuplicateKeyException(LogicPrimaryKeyValidator.Result result, String message) {
         super(message, 400);
-        this.data = data;
+        this.result = result;
     }
 }

+ 3 - 3
hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKey.java

@@ -16,11 +16,11 @@ import java.lang.annotation.*;
 public @interface LogicPrimaryKey {
 
     /**
-     * 属性名称,如果注解在类上,值为空的时候必须指定{@link this#group()},并且group对应的类上必须有合法的LogicPrimaryKey注解
+     * 属性名称,如果注解在类上,值为空的时候必须指定{@link this#groups()},并且group对应的类上必须有合法的LogicPrimaryKey注解
      *
      * @return 属性名集合, 在字段上此属性无效
      */
-    String[] value() default "";
+    String[] value() default {};
 
     /**
      * 验证条件,值为一个spel表达式,可在验证的时候根据实体中不同的属性,使用不同的规则,例如:
@@ -51,6 +51,6 @@ public @interface LogicPrimaryKey {
      *
      * @return 分组
      */
-    Class group() default Void.class;
+    Class[] groups() default Void.class;
 
 }

+ 62 - 1
hsweb-core/src/main/java/org/hswebframework/web/validator/LogicPrimaryKeyValidator.java

@@ -1,5 +1,66 @@
 package org.hswebframework.web.validator;
 
+import lombok.Getter;
+import lombok.Setter;
+import lombok.SneakyThrows;
+
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * 逻辑主键验证器
+ *
+ * @see LogicPrimaryKey
+ * @see Result
+ */
 public interface LogicPrimaryKeyValidator {
-    void validate(Object bean, Class... group);
+
+    <T> Result<T> validate(T bean, Class... group);
+
+    @Getter
+    @Setter
+    class Result<T> {
+
+        public static Result passed() {
+            Result result = new Result();
+            result.setError(false);
+            return result;
+        }
+
+        private boolean error=false;
+
+        private T data;
+
+        private Map<String, Object> properties;
+
+        private String message = "存在相同数据";
+
+        public boolean isPassed() {
+            return !error;
+        }
+
+        public void ifPassed(Consumer<Result<T>> consumer) {
+            if (isPassed()) {
+                consumer.accept(this);
+            }
+        }
+
+        public void ifError(Consumer<Result<T>> consumer) {
+            if (isError()) {
+                consumer.accept(this);
+            }
+        }
+
+        public void ifErrorThrow() {
+            ifErrorThrow(DuplicateKeyException::new);
+        }
+
+        @SneakyThrows
+        public void ifErrorThrow(Function<Result<T>, Exception> exceptionGetter) {
+            if (isError()) {
+                throw exceptionGetter.apply(this);
+            }
+        }
+    }
 }

+ 1 - 1
hsweb-system/hsweb-system-authorization/hsweb-system-authorization-local/src/main/java/org/hswebframework/web/service/authorization/simple/SimpleMenuGroupBindService.java

@@ -47,7 +47,7 @@ public class SimpleMenuGroupBindService extends AbstractTreeSortService<MenuGrou
 
     @Override
     public int deleteByGroupId(String groupId) {
-        tryValidateProperty(groupId != null, MenuGroupBindEntity.groupId, "group id can not be null");
+        tryValidateProperty(groupId != null, MenuGroupBindEntity.groupId, "groups id can not be null");
         return createDelete().where(MenuGroupBindEntity.groupId, groupId).exec();
     }
 }