Implement search filter with conditions

前端 未结 1 414
悲&欢浪女
悲&欢浪女 2021-01-17 09:18

I want to implement search functionality with several sub conditions. I tried this:

    @GetMapping(\"find\")
    public Page g         


        
相关标签:
1条回答
  • 2021-01-17 09:43

    If you want to create your very special filter I believe you should start with invention of your search interface. For example like that:

    GET /models?name=eq(john smith)&createdAt=between(2019-01-01,2019-01-31)
    GET /models?name=like(sm)&createdAt=from(2019-01-01)
    GET /models?name=sw(john)&createdAt=to(2019-01-31)
    

    After that, you will be able to try to implement it.

    IMO the best way to solve such a task is to use Spring Data JPA Specifications (and JPA Criteria API). For example:

    1) Let's create a Filter class that implements Specification for our entity Model:

    @Value
    public class ModelFilter implements Specification<Model> {
    
        private String name;
        private String createdAt;
    
        @Override
        public Predicate toPredicate(Root<Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    
            List<Predicate> predicates = new ArrayList<>();
    
            // Prepare predicates and fill the list with them...
    
            return builder.and(predicates.toArray(new Predicate[0]));
        }
    }
    

    2) Then create a controller method:

    @GetMapping
    public List<Model> getAllByFilter(ModelFilter filter) {
        return repo.findAll(filter); 
    }
    

    All that's left to do is prepare our predicates ))

    To make this, we can create a handy 'Predicate Builder' interface first:

    @FunctionalInterface
    interface PredicateBuilder<T> {
    
        Optional<Predicate> get(String fieldName, String value, Root<T> root, CriteriaBuilder builder);
    
        static Matcher getMatcher(String op, String value) {
            return getMatcher(op, value, "(.+)");
        }
    
        static Matcher getMatcher(String op, String value, String pattern) {
            return Pattern.compile(op + "\\(" + pattern + "\\)").matcher(value);
        }
    }
    

    Then try to make our predicates:

    equal

    PredicateBuilder<Model> eq = (fieldName, value, root, cb) -> {
        Matcher m = getMatcher("eq", value);
        if (m.matches()) {
            return Optional.of(cb.equal(cb.upper(root.get(fieldName)), m.group(1).toUpperCase()));
        } else {
            return Optional.empty();
        }
    };
    

    like

    PredicateBuilder<Model> like = (fn, value, root, cb) -> {
        Matcher m = getMatcher("like", value);
        if (m.matches()) {
            return Optional.of(cb.like(cb.upper(root.get(fn)), "%" + m.group(1).toUpperCase() + "%"));
        } else {
            return Optional.empty();
        }
    };
    

    start with

    PredicateBuilder<Model> sw = (fn, value, root, cb) -> {
        Matcher m = getMatcher("sw", value);
        if (m.matches()) {
            return Optional.of(cb.like(cb.upper(root.get(fn)), m.group(1).toUpperCase() + "%"));
        } else {
            return Optional.empty();
        }
    };
    

    between

    PredicateBuilder<Model> between = (fn, value, root, cb) -> {
        Matcher m = getMatcher("between", value, "(.+)\\s*,\\s*(.+)");
        if (m.matches()) {
            LocalDate from = LocalDate.parse(m.group(1));
            LocalDate to = LocalDate.parse(m.group(2));
            return Optional.of(cb.between(root.get(fn), from, to));
        } else {
            return Optional.empty();
        }
    };
    

    from

    PredicateBuilder<Model> from = (fn, value, root, cb) -> {
        Matcher m = getMatcher("from", value);
        if (m.matches()) {
            LocalDate from = LocalDate.parse(m.group(1));
            return Optional.of(cb.greaterThanOrEqualTo(root.get(fn), from));
        } else {
            return Optional.empty();
        }
    };
    

    to

    PredicateBuilder<Model> to = (fn, value, root, cb) -> {
        Matcher m = getMatcher("to", value);
        if (m.matches()) {
            LocalDate to = LocalDate.parse(m.group(1));
            return Optional.of(cb.lessThanOrEqualTo(root.get(fn), to));
        } else {
            return Optional.empty();
        }
    };
    

    It remains only to complete the Filter class:

    @Value
    public class ModelFilter implements Specification<Model> {
    
        private String name;
        private String createdAt;
    
        PredicateBuilder<Model> eq = ... ;
        PredicateBuilder<Model> like = ... ;
        PredicateBuilder<Model> sw = ... ;
        PredicateBuilder<Model> between = ... ;
        PredicateBuilder<Model> from = ... ;
        PredicateBuilder<Model> to = ... ;
    
        @Override
        public Predicate toPredicate(Root<Model> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
    
            List<Predicate> predicates = new ArrayList<>();
    
            if (name != null) {
                eq.get("name", name, root, builder).ifPresent(predicates::add);
                like.get("name", name, root, builder).ifPresent(predicates::add);
                sw.get("name", name, root, builder).ifPresent(predicates::add);
            }
    
            if (createdAt != null) {
                between.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
                from.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
                to.get("createdAt", createdAt, root, builder).ifPresent(predicates::add);
            }
    
            return builder.and(predicates.toArray(new Predicate[0]));
        }
    }
    

    Of course it's just one example of implementation. You can create your very own implementation of specifications and predicates you need. The main things here are:

    • come up with your search interface
    • make your 'filter' Specification
    • prepare all the predicates you need
    • use the filter specification in your controller method
    0 讨论(0)
提交回复
热议问题