java bean转换成jpa所需的过滤条件(Predicate)

需求场景:

不管业务多么复杂,系统架构多么复杂,最终都是对各类数据库的增删改查,现阶段研发的协同研发平台采用微服务架构,ORM层采用spring-boot-starter-data-jpa,比如,系统管理服务中的用户管理需要支持对用户的按照多个不同字段组合查询,前端的页面往往是提供一个查询表单,如username,gender,birthdateage等,遵循restful约定,后端提供的接口通常是Get请求类型的,为了适配前端的多参数组合查询请求,最先想到的是这样的:

@RestController("/user")
public class UserController{

    @ApiOperation(value = "查询用户列表")
    @GetMapping
    public PageQueryResponseData list(@RequestParam(defaultValue = "1") Integer page,
                                              @RequestParam(defaultValue = "25") Integer limit,
                                              @RequestParam(defaultValue = "") 
String username,
                                              @RequestParam(defaultValue = "") 
String gender, 
                                              @RequestParam(defaultValue = "") 
String birthdate, 
                                              @RequestParam(defaultValue = "") 
String age,                  ) {
        PageQueryResponseData result = userService.listToVO(page, limit, params);
        return result;
    }
}

这样带来的局限性:

  • 没有扩展性;
  • service层充斥着大量的if/else代码,目的仅仅是为了聚合查询条件
  • 各个参数的查询运算符没有描述,比如username对应的是数据库中的like还是equal查询

一种解决方案:

  • 定义一个bean组合所有的可能的查询条件,接口中直接通过bean接收前端的参数,这样接口简化成这样:
@ApiOperation(value = "查询用户列表")
@ApiImplicitParams({
        @ApiImplicitParam(name = "page", value = "第几页", required = true, dataType = "Integer"),
        @ApiImplicitParam(name = "limit", value = "每一页显示条数", required = true, dataType = "Integer")
})
@LogAnnotation(isSaveRequestData = true, isSaveResponseData = true)
@GetMapping
public PageQueryResponseData list(@RequestParam(defaultValue = "1") Integer page,
                                          @RequestParam(defaultValue = "25") Integer limit,
                                          @ModelAttribute DefaultUserQryWrapper params) {
    PageQueryResponseData result = userService.listToVO(page, limit, params);
    return result;
}
  • 封装的查询bean:DefaultUserQryWrapper.java
@Data
@ApiModel(value="用户查询封装,默认分页,page,limit")
@Builder
public final class DefaultUserQryWrapper implements BaseQryWrapperInterface {
    private static final long serialVersionUID = 1543383274516194973L;
    @Tolerate
 public DefaultUserQryWrapper(){}
    @QueryCriteria(sqlOperator = SqlOperator.ge)
    @OrderBy(direction = DirectionEnum.DESCENDING)
    private Long id;
    @QueryCriteria(sqlOperator = SqlOperator.in)
    private Set grade;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @ApiModelProperty(value="出生日期(结束)")
    @QueryCriteria(sqlOperator = SqlOperator.betweenAndTo,column = "birthday")
    private Date birthdayTo;
    @ApiModelProperty(value="部门Id")
    @QueryCriteria(sqlOperator = SqlOperator.in)
    private Set deptId;
    @ApiModelProperty(value="用户名查询")
    @QueryCriteria(sqlOperator = SqlOperator.like)
    private String username;
    @ApiModelProperty(value="姓名")
    @QueryCriteria(sqlOperator = SqlOperator.like)
    private String fullName;
}
  • 单独抽取一个工具类,对DefaultUserQryWrapper处理,处理成jpa的查询条件specification:
@Slf4j
public class JPAQueryUtil {
    /**
 * 获取一个类及其父类的所有字段(属性,field)
 *
 * @param clazz invoked class
 * @return fields include self and all parent */ private static List getAllFieldsWithRoot(Class clazz) {
        List fieldList = new ArrayList<>();
        //get from this class self
 Field[] dFields = clazz.getDeclaredFields();
        if (dFields.length > 0)
            fieldList.addAll(Arrays.asList(dFields));
        Class superClass = clazz.getSuperclass();
        // if parent class is Object.class ,return ..
 if (superClass == Object.class) return Arrays.asList(dFields);
        // recursive to get from parent class
 List superFields = getAllFieldsWithRoot(superClass);
        if (!superFields.isEmpty()) {
            superFields.stream().
                    filter(field -> !fieldList.contains(field)).
                    forEach(fieldList::add);
        }
        return fieldList;
    }
    //动态查询or连接
 public static  Specification toJPASpecificationWithOr(Q queryWrapper, Class entityCls) {
        return toJPASpecificationWithLogic("or", queryWrapper, entityCls);
    }
    //动态查询and连接
 public static  Specification toJPASpecificationWithAnd(Q queryWrapper, Class entityCls) {
        return toJPASpecificationWithLogic("and", queryWrapper, entityCls);
    }
    //logicType or/and
 @SuppressWarnings({"rawtypes", "unchecked"})
    public static  Specification toJPASpecificationWithLogic(String logicType, Q queryWrapper, Class entityCls) {
        return (root, criteriaQuery, criteriaBuilder) -> {
            Class clazz = queryWrapper.getClass();
            //获取查询类Query的所有字段,包括父类字段
 List fields = getAllFieldsWithRoot(clazz);
            List predicates = new ArrayList<>(fields.size());
            Predicate predicate = null;
            //wrapper中的column必须在实体类中存在,否则忽略!
 List supportedFields = getAllFieldsWithRoot(entityCls);
            Set supportFieldNames = supportedFields.stream().map(Field::getName).collect(Collectors.toSet());
            if (CollUtil.isEmpty(supportFieldNames)) {
                return predicate;
            }
            //init betweenAnd 用map存放between and 的字段对,from和to必须指向同一个column!
 Map> betweenAndMap = new HashMap<>();
            Map targetField;
            for (Field field : fields) {
                QueryCriteria queryCriteria = field.getAnnotation(QueryCriteria.class);
                if (queryCriteria == null) {
                    continue;
                }
                String column = queryCriteria.column();
                if (column.equals("")) {
                    column = field.getName();
                }
                if (!supportFieldNames.contains(column)) {
                    continue;
                }
                Path path = root.get(column);
                if (queryCriteria.sqlOperator() == SqlOperator.isNull) {
                    predicates.add(criteriaBuilder.isNull(path));
                    continue;
                }
                if (queryCriteria.sqlOperator() == SqlOperator.isNotNull) {
                    predicates.add(criteriaBuilder.isNotNull(path));
                    continue;
                }
                field.setAccessible(true);
                try {
                    // nullable
 Object value = field.get(queryWrapper);
                    //如果值为null,注解未标注nullable,跳过
 if (value == null && !queryCriteria.nullAble())
                        continue;
                    // can be empty
 if (value != null && String.class.isAssignableFrom(value.getClass())) {
                        String s = (String) value;
                        if (s.equals("") && !queryCriteria.emptyAble()) {
                            continue;
                        }
                    }
                    switch (queryCriteria.sqlOperator()) {
                        case equal:
                            predicates.add(criteriaBuilder.equal(path, value));
                            break;
                        case like:
                            predicates.add(criteriaBuilder.like(path, "%" + value + "%"));
                            break;
                        case gt:
                            predicates.add(criteriaBuilder.gt(path, (Number) value));
                            break;
                        case lt:
                            predicates.add(criteriaBuilder.lt(path, (Number) value));
                            break;
                        case ge:
                            predicates.add(criteriaBuilder.ge(path, (Number) value));
                            break;
                        case le:
                            predicates.add(criteriaBuilder.le(path, (Number) value));
                            break;
                        case notEqual:
                            predicates.add(criteriaBuilder.notEqual(path, value));
                            break;
                        case notLike:
                            predicates.add(criteriaBuilder.notLike(path, "%" + value + "%"));
                            break;
                        case greaterThan:
                            predicates.add(criteriaBuilder.greaterThan(path, (Comparable) value));
                            break;
                        case greaterThanOrEqualTo:
                            predicates.add(criteriaBuilder.greaterThanOrEqualTo(path, (Comparable) value));
                            break;
                        case lessThan:
                            predicates.add(criteriaBuilder.lessThan(path, (Comparable) value));
                            break;
                        case lessThanOrEqualTo:
                            predicates.add(criteriaBuilder.lessThanOrEqualTo(path, (Comparable) value));
                            break;
                        case in://in 查询 wrpper中的字段值必须为Collection形式
 if (!Collection.class.isAssignableFrom(field.getType())) {
                                log.warn("a search attribute named {} was ignored ! because for in Query,the value of search attribute must extend Collection", column);
                                break;
                            }
                            if (value instanceof Collection) {
                                Collection inValues = (Collection) value;
                                CriteriaBuilder.In in = criteriaBuilder.in(path);
                                for (Object inValue : inValues) {
                                    in.value(inValue);
                                }
                                predicates.add(in);
                                break;
                            }
                            if (null != value && value.getClass().isArray()) {
                                Object[] array = (Object[]) value;
                                CriteriaBuilder.In in = criteriaBuilder.in(path);
                                for (Object t : array) {
                                    in.value(t);
                                }
                                predicates.add(in);
                            }
                            break;
                        case betweenAndFrom:
                            targetField = betweenAndMap.get(column);
                            if (ObjectUtil.isEmpty(targetField)) {
                                targetField = new HashMap<>();
                                betweenAndMap.put(column, targetField);
                            }
                            targetField.put("from", value);
                            break;
                        case betweenAndTo:
                            targetField = betweenAndMap.get(column);
                            if (ObjectUtil.isEmpty(targetField)) {
                                targetField = new HashMap<>();
                                betweenAndMap.put(column, targetField);
                            }
                            targetField.put("to", value);
                            break;
                        default:
                    }
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
            //处理between and
 List betweenAndPredicates = handleBetweenAndPredicates(betweenAndMap, root, criteriaBuilder);
            predicates.addAll(betweenAndPredicates);
            if (logicType == null || logicType.equals("") || logicType.equals("and")) {
                predicate = criteriaBuilder.and(predicates.toArray(new Predicate[0]));//and连接
 } else if (logicType.equals("or")) {
                predicate = criteriaBuilder.or(predicates.toArray(new Predicate[0]));//or连接
 }
            return predicate;
        };
    }
    @SuppressWarnings("unchecked")
    private static List handleBetweenAndPredicates(Map> betweenAndMap, Root root, CriteriaBuilder criteriaBuilder) {
        List betweenPredicates = new ArrayList<>();
        if (!MapUtil.isEmpty(betweenAndMap)) {
            for (Map.Entry> entry : betweenAndMap.entrySet()) {
                String column = entry.getKey();
                Map criteriaMap = entry.getValue();
                Object from = criteriaMap.get("from");
                Object to = criteriaMap.get("to");
                if (ObjectUtil.isEmpty(from) && ObjectUtil.isEmpty(to)) {
                    continue;
                }
                //这里只检查日期或者数值类型
 Class valueType = ObjectUtil.isEmpty(from) ? to.getClass() : from.getClass();
                if (Date.class.isAssignableFrom(valueType)) {
                    //如果日期查询中有from和to 有空的,用当前时间代替
 Path path = root.get(column);
                    from = Optional.ofNullable(from).orElse(new Date());
                    to = Optional.ofNullable(to).orElse(new Date());
                    betweenPredicates.add(criteriaBuilder.between(
                            path, (Date) from, (Date) to));
                    continue;
                }
                //数值类型,如果有空的,则from = to
 if (Integer.class.isAssignableFrom(valueType)) {
                    Path path = root.get(column);
                    betweenPredicates.add(criteriaBuilder.between(
                            path, Convert.toInt(from), Convert.toInt(to)));
                    continue;
                }
                log.warn("BetweenAnd Search Attribute was ignored,caused by unsupported field type {}", valueType);
            }
        }
        return betweenPredicates;
    }
}

这样使用:

Specification rawSpecif = JPAQueryUtil.toJPASpecificationWithAnd(useQry, UserPO.class);
Optional existedOne = userDORepository.findOne(rawSpecif);

你可能感兴趣的:(java bean转换成jpa所需的过滤条件(Predicate))