Mybatis+自定义注解,优雅的实现条件查询

时间:2022-12-21 19:03:40

Springboot+Mybatis实现条件查询可以这样实现

@Data
@NoArgsConstructor
@ApiModel("查询DTO")
public class QueryDTO  {
   
    @ApiModelProperty("名称")
    private String name;
     
    @ApiModelProperty("类型")
    private String type;

     
    @ApiModelProperty("描述")
    private String description;

}

Controller代码如下

    @GetMapping("/list")
    @ApiOperation(value = "列表查询")
    public Result list(@ApiParam QueryDTO request){
   
        QueryWrapper<QueryDTO> queryWrapper = new QueryWrapper<>();
        if(request.getName()!=null){
            queryWrapper.like("name",request.getName());
        }
        if(request.getType()!=null){
            queryWrapper.eq("type",request.getType());
        }
        if(request.getDescription()!=null){
            queryWrapper.eq("descrption",request.getDescription());
        }
        List<QueryDTO> list = service.list(queryWrapper);
        return Result.success(list);
    }

可见,每多一个条件,就要多写一段 queryWrapper.eq...这样类似的代码

使用自定义注解可以更优雅的实现条件查询

添加一个自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Query {
    /**
     * 对应数据库字段,为空取实体属性名 驼峰转下划线
     */
    String column() default "";

    /**
     * 另一个类中的属性名称,支持多级获取,以小数点隔开
     */
    String targetAttr() default "";

    /**
     * 查询类型
     */
    Type type() default Type.EQUAL;

    enum Type {
        /**
         * 相等
         */
        EQUAL((queryWrapper, filedColumn, val) -> {
            queryWrapper.eq(filedColumn, val);
        }),
        /**
         * 大于等于
         */
        GREATER_THAN((queryWrapper, filedColumn, val) -> {
            queryWrapper.ge(filedColumn, val);
        }),
        /**
         * 大于
         */
        GREATER_THAN_NQ((queryWrapper, filedColumn, val) -> {
            queryWrapper.gt(filedColumn, val);
        }),
        /**
         * 小于等于
         */
        LESS_THAN((queryWrapper, filedColumn, val) -> {
            queryWrapper.le(filedColumn, val);
        }),
        /**
         * 小于
         */
        LESS_THAN_NQ((queryWrapper, filedColumn, val) -> {
            queryWrapper.lt(filedColumn, val);
        }),
        /**
         * 不等于
         */
        NOT_EQUAL((queryWrapper, filedColumn, val) -> {
            queryWrapper.ne(filedColumn, val);
        }),
        /**
         * 不为空
         */
        NOT_NULL((queryWrapper, filedColumn, val) -> {
            queryWrapper.isNotNull(filedColumn);
        }),
        /**
         * 中模糊查询<br/>
         * like %str%
         */
        LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.like(filedColumn, val);
        }),
        /**
         * 左模糊查询
         */
        LEFT_LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.likeLeft(filedColumn, val);
        }),
        /**
         * 右模糊查询
         */
        RIGHT_LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.likeRight(filedColumn, val);
        }),
        /**
         * 包含
         */
        IN((queryWrapper, filedColumn, val) -> {
            queryWrapper.in(filedColumn, (Collection) val);
        }),
        /**
         * JSON数组中是否包含
         */
        JSON_ARRAY_CONTAINS((queryWrapper, filedColumn, val) -> {
            queryWrapper.apply("JSON_CONTAINS(" + filedColumn + ",json_array({0}) )", val);
        }),
        ;
        private TriConsumer consumer;

        Type(TriConsumer<QueryWrapper, String, Object> consumer) {
            this.consumer = consumer;
        }

        public void buildQuery(QueryWrapper t, String field, Object val) {
            consumer.accept(t, field, val);
        }
    }
}
如果字段名与属性名相同或相匹配(驼峰转下划线 如:createTime-> create_time 表示字段匹配),不用标注column字段
如果字段名与属性名不同且不匹配,如属性名为 searchValue,需要查询的字段是name,则需要增加column字段,@Query(column="name")

默认使用=查询,如果要使用like或其它查询类型,则需要标注type字段,选择相应的type即可,如: @Query(type=Query.Type.LIKE) 
每个type会生成什么样的QueryWrapper语句,可以参考Type枚举类的方法

再添加一个处理注解的工具类

@Slf4j
public class QueryUtil {
    public static <T> QueryWrapper setQueryWrapper(T bean) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        if (bean == null) {
            return queryWrapper;
        }
        try {
            List<Object[]> fields = getFields(bean.getClass());
            for (Object[] os : fields) {
                Field field = (Field) os[0];
                Query q = (Query) os[1];
                Object val = getTargetValue(bean, field, q);
                if (val == null) {
                    continue;
                }
                String filedColumn = StringUtils.isBlank(q.column()) ? StringUtils.toUnderScoreCase(field.getName()) : q.column();
                q.type().buildQuery(queryWrapper,filedColumn,val);
            }
        } catch (Exception e) {
            log.error("设置查询条件失败{}", e);
        }
        return queryWrapper;
    }

    /**
     * 获取bean值
     *
     * @param vo
     * @param field
     * @return
     * @throws Exception
     */
    public static Object getObjectValue(Object vo, Field field) throws Exception {
        Object o = field.get(vo);
        if (o == null) {
            return null;
        }
        //空字符串转换为null对象
        if (field.getGenericType().toString().equals("class java.lang.String")) {
            String val = (String) o;
            if (StringUtils.isBlank(val)) {
                o = null;
            }
        } 
        return o;
    }

    /**
     * 获取bean中的属性值
     *
     * @param vo    实体对象
     * @param field 字段
     * @param query 注解
     * @return 最终的属性值
     * @throws Exception
     */
    private static Object getTargetValue(Object vo, Field field, Query query) throws Exception {
        Object o = getObjectValue(vo, field);
        if (o == null) {
            return o;
        }
        if (StringUtils.isNotEmpty(query.targetAttr())) {
            String target = query.targetAttr();
            if (target.contains(".")) {
                String[] targets = target.split("[.]");
                for (String name : targets) {
                    o = getValue(o, name);
                }
            } else {
                o = getValue(o, target);
            }
        }
        return o;
    }

    /**
     * 以类的属性的get方法方法形式获取值
     *
     * @param o
     * @param name
     * @return value
     * @throws Exception
     */
    private static Object getValue(Object o, String name) throws Exception {
        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) {
            if (o instanceof Map) {
                Map<String, Object> objectMap = BeanUtils.beanToMap(o);
                if (objectMap.containsKey(name)) {
                    return objectMap.get(name);
                }
                return null;
            } else {
                Class<?> clazz = o.getClass();
                Field field = clazz.getDeclaredField(name);
                field.setAccessible(true);
                o = getObjectValue(o, field);
            }
        }
        return o;
    }

    /**
     * 获取字段注解信息
     */
    public static List<Object[]> getFields(Class clazz) {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Query.class)) {
                Query attr = field.getAnnotation(Query.class);
                if (attr != null) {
                    field.setAccessible(true);
                    fields.add(new Object[]{field, attr});
                }
            }

            // 多注解
            if (field.isAnnotationPresent(Queries.class)) {
                Queries attrs = field.getAnnotation(Queries.class);
                Query[] queries = attrs.value();
                for (Query attr : queries) {
                    if (attr != null) {
                        field.setAccessible(true);
                        fields.add(new Object[]{field, attr});
                    }
                }
            }
        }
        return fields;
    }

}

在查询的DTO类中使用注解

@Data
@NoArgsConstructor
@ApiModel("查询DTO")
public class QueryDTO  {
    /**
     * 名称
     */
    @ApiModelProperty("名称")
    @Query(type = Query.Type.LIKE) //不是用等于查找条件,需要指定type
    private String name;
    /**
     * 类型
     */
    @ApiModelProperty("类型")
    @Query  //表字段与属性名相匹配,且是用等于查找的,只需要这样的简单注解即可
    private String type;

    /**
     * 描述
     */
    @ApiModelProperty("描述")
    @Query(column="description") //如果字段名与属性名不匹配,则添加column字段值指定表字段
    private String description;

}

Controller类中使用如下:

    @GetMapping("/list")
    @ApiOperation(value = "列表查询")
    public Result list(@ApiParam QueryDTO request){
   
        QueryWrapper<QueryDTO> queryWrapper = QueryUtil.setQueryWrapper(request);
        List<QueryDTO> list = service.list(queryWrapper);
        return Result.success(list);
    }

Controller中的代码更简洁了,如果需要增加一个条件,只需要在QueryDTO中增加一处属性和注解即可,不用再复制一段雷同的代码