V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Mrzhs
V2EX  ›  Java

Java , 写了一个生成模糊查询和时间区间查询 wrapper 的函数,有什么优化建议或是隐藏的坑吗?

  •  
  •   Mrzhs · 255 天前 · 1869 次点击
    这是一个创建于 255 天前的主题,其中的信息可能已经有所发展或是发生改变。

    模糊查询大致思路是先遍历实体类,然后实体类如果有值,则获取它的 TableField 注解,获取字段名,然后拼接 wrapper 。
    时间查询的大体思路和上面差不多,前端传来 xxxStart 和 xxxxEnd, 首先保存到实体类中,然后再做字符拼接。
    想起写这个的原因是因为基本每个前端 Table 都需要类似的查询,本来是写在 XML 文件里,但是每改一次字段都会联动着更改 XML ,太过繁琐,增加出错几率。 所以写了这么一个工具类来通过实体类动态拼接模糊查询。

    //拼接函数
    /**
         * 通过实体类生成模糊搜索和日期区间查询的 wrapper
         * @param cls   类名
         * @param entity    实体类
         * @param orderByField  排序字段
         * @param orderByAsc    是否升序
         * @return  QueryWrapper
         * @param <T>   实体类
         */
        public <T> QueryWrapper<T> generateFuzzySearchAndBetweenDateWrapper(Class<T> cls, T entity, String orderByField, Boolean orderByAsc) {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
    
            //先检查是否排序
            if (!DataUtil.isEmpty(orderByField)) {
                wrapper.orderBy(true, orderByAsc, StringFormat.camelToUnderscore(orderByField));
            }
            //再检查实体类是否为空
            if (DataUtil.isEmpty(entity)) {
                return wrapper;
            }
            try {
                //遍历实体类的所有字段. getAllFields 方法获取不止当前类的字段, 还包括父类的字段
                for (Field field: DataUtil.getAllFields(cls)) {
                    //如果是 final, static, transient 修饰的字段, 则跳过. 一般用来排除 private static final long serialVersionUID = 1L; 序列化 id
                    if (Modifier.isFinal(field.getModifiers())
                            || Modifier.isStatic(field.getModifiers())
                            || Modifier.isTransient(field.getModifiers())) {
                        continue;
                    }
                    //获取字段的 get 方法
                    Method getFunc = cls.getMethod("get" + StringFormat.capitalize(field.getName()));
                    Object value = getFunc.invoke(entity);
                    //如果字段有 TableField 注解, 并且值不为空
                    if (field.isAnnotationPresent(TableField.class) && !DataUtil.isEmpty(value)) {
                        //如果 TableField 注解的 exist 属性为 true, 则使用注解的 value 作为字段名
                        if (field.getAnnotation(TableField.class).exist()) {
                            String column = field.getAnnotation(TableField.class).value();
                            wrapper.like(column, value);
                        } else {
                            //如果 TableField 注解的 exist 属性为 false, 证明可能是一个时间区域查询
                            String name = field.getName();
                            if (name.endsWith("Start")) {
                                String column = name.substring(0, name.length() - 5);
                                wrapper.ge(StringFormat.camelToUnderscore(column), value);
                            } else if (name.endsWith("End")) {
                                String column = name.substring(0, name.length() - 3);
                                wrapper.le(StringFormat.camelToUnderscore(column), value);
                            }
                        }
                    }
    
                }
            } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
            return wrapper;
        }
    
    
    
    //controller
    
    @GetMapping("select/page")
        public IPage<Company> selectPage(@RequestBody(required = false) Company company,
                                        @RequestParam(defaultValue = "1") Integer current,
                                        @RequestParam(defaultValue = "10") Integer size,
                                        @RequestParam(required = false)String orderByField,
                                        @RequestParam(defaultValue = "true")Boolean orderByAsc) {
            QueryWrapper<Company> companyQueryWrapper = generateFuzzySearchAndBetweenDateWrapper(Company.class, company, orderByField,orderByAsc);
            Page<Company> companyPage = new Page<>();
            companyPage.setCurrent(current);
            companyPage.setSize(size);
            IPage<Company> companyIPage = companyService.page(companyPage, companyQueryWrapper);
            return companyIPage;
        }
    

    已知的问题:
    1.Get 请求携带请求体,功能是可以实现,但似乎有些不合规矩,隐隐约约记得当时学 J2EE 的时候有讲过不要在 Get 请求携带请求体
    2.每个时间字段都需要增加两个虚拟属性,用于接受前端发来的时间范围数据

    
    		@TableField("expire_date")
            @ApiModelProperty(value = "到期时间")
            @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
            private Date expireDate;
    
            @TableField(exist = false)
            private String expireDateStart;
    
            @TableField(exist = false)
            private String expireDateEnd;
    

    3.想实现数据字典的功能,但是感觉有点复杂, 首先需要调用其他 Service 来查询数据表,然后实体类还需要多加一个虚拟字段,用于存储数据字典对应的值。例如:

    		
            @TableField("industry_type")
            @ApiModelProperty(value = "企业类型")
            private Integer industryType;
    
    		@TableField(exist = false)
    		private String industryTypeValue;
    
    第 1 条附言  ·  254 天前

    又改了一下,貌似可以不加Start和End的虚拟字段了。

        public <T> QueryWrapper<T> generateFuzzySearchAndBetweenDateWrapper(Class<T> cls, Map<String, Object> param, String orderByField, Boolean orderByAsc) {
            QueryWrapper<T> wrapper = new QueryWrapper<>();
            if (!DataUtil.isEmpty(orderByField)) {
                wrapper.orderBy(true, orderByAsc, StringFormat.camelToUnderscore(orderByField));
            }
            if (DataUtil.isEmpty(param)) {
                return wrapper;
            }
            for (Field field : DataUtil.getAllFields(cls)) {
                //如果是final, static, transient修饰的字段, 则跳过. 一般用来排除private static final long serialVersionUID = 1L; 序列化id
                if (Modifier.isFinal(field.getModifiers())
                        || Modifier.isStatic(field.getModifiers())
                        || Modifier.isTransient(field.getModifiers())
                        || !field.isAnnotationPresent(TableField.class)) {
                    continue;
                }
                String column = field.getAnnotation(TableField.class).value();
                if (DataUtil.isEmpty(column)) {
                    continue;
                }
                if (field.getType() == Date.class) {
                    Object start = param.get(field.getName() + "Start");
                    if (start != null) {
                        wrapper.ge(column, start);
                    }
                    Object end = param.get(field.getName() + "End");
                    if (end != null) {
                        wrapper.le(column, end);
                    }
                    continue;
                }
                Object value = param.get(field.getName());
                if (!DataUtil.isEmpty(value)) {
                    if (field.getAnnotation(TableField.class).exist()) {
                        wrapper.like(column, value);
                    }
                }
    
            }
            return wrapper;
        }
    
    7 条回复    2024-04-16 11:15:43 +08:00
    running17
        1
    running17  
       255 天前
    俺的思路是做成一个新的注解,注解携带对应筛选的字段名和筛选的类型,直接解析这个注释来做这个逻辑;直接用 name 判断万一有业务是用 Start 和 End 的字段那就尴尬了;然后反射的操作记得用 map 之类的缓存起来,没有必要每次都去反射然后判断
    Mrzhs
        2
    Mrzhs  
    OP
       255 天前
    @running17 虽然但是,如果通过注解来实现这两个字段的功能, 首先问题是, 如何将前端请求的 XXXStart 映射到注解里呢? Controller 貌似没有提供相关的功能
    jshfsym42
        3
    jshfsym42  
       255 天前
    @Mrzhs 我的理解是,以时间范围查询为例,在实体类的字段上标注解,指示当前字段用于范围查询
    @RangeQuery
    private Date expireDate;
    前端直接 get 请求传实体类名 queryEntity ,和开始截止时间 startDate,endDate
    解析实体类名,拿到实体类,解析到实体类哪个字段有 @RangeQuery 注解用于时间范围查询,根据前端的开始截止时间拼接 wrapper 。
    Ayanokouji
        4
    Ayanokouji  
       255 天前
    spring 支持用 object 来接收 get 的 query param
    Mrzhs
        5
    Mrzhs  
    OP
       254 天前
    @Ayanokouji 是的. 但是不太清楚有没有什么弊端
    Ayanokouji
        6
    Ayanokouji  
       254 天前   ❤️ 1
    @Mrzhs 这个有啥弊端,就是绑定,比如 validtor 注解,你自定义的注解也可以加到这个对象或对象的字段上
    SANJI59
        7
    SANJI59  
       254 天前
    查询条件单独封装成实体,有同参数的定义成父类由子类查询体继承比如开始结束这种时间查询统一命名,然后时间属性这个字段可以由前端来传给你,尽量和库里时间字段保持一致,这样还支持多个时间字段的查询,不用定死。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2752 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 15:21 · PVG 23:21 · LAX 07:21 · JFK 10:21
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.