使用条件实现搜索过滤器

Posted

技术标签:

【中文标题】使用条件实现搜索过滤器【英文标题】:Implement search filter with conditions 【发布时间】:2019-09-19 01:19:24 【问题描述】:

我想用几个子条件实现搜索功能。我试过这个:

    @GetMapping("find")
    public Page<PaymentTransactionsDTO> getAllBySpecification(
            @And(
                    @Spec(path = "name", spec = LikeIgnoreCase.class),
                    @Spec(path = "unique_id", spec = LikeIgnoreCase.class),
                    @Spec(path = "createdAt", params = "from", spec = GreaterThanOrEqual.class),
                    @Spec(path = "createdAt", params = "to", spec = LessThanOrEqual.class)
            ) Specification<PaymentTransactions> specification,
            Pageable pageable
    )         
        return transactionService.getAllBySpecification(specification, pageable));       
    

存储库:

      @Override
      public Page<PaymentTransactions> getAllBySpecification(final Specification<PaymentTransactions> specification, final Pageable pageable) 
          return dao.findAll(specification, pageable);
      

目前此请求正在运行:

GET /api/transactions/find?unique_id=22&page=0&size=10 

但我还想实现这些额外的搜索条件,而不仅仅是发送unique_id的基本搜索:

start with 
=
end with 
contains

使用https://github.com/tkaczmarzyk/specification-arg-resolver 有没有办法发送额外的子条件?我一般找不到此问题的解决方案,发送这些值的最佳做法是什么?

【问题讨论】:

我建议您不要在方法定义级别使用注释来处理这个问题,而是让所有参数传递并实现逻辑或表达式语言验证器来处理可能的组合。 @Nikolas 感谢您的回复。请给我看代码示例好吗? 你有没有试过先写一些代码? 查看上面我尝试实现的代码。我不知道如何规范上述情况。 我指出我不建议你继续使用注释的方式,我建议你实现不同的机制。请仔细阅读。 【参考方案1】:

如果您想创建非常特殊的过滤器,我认为您应该从发明搜索界面开始。比如这样:

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)

之后,您将能够尝试实现它。

IMO 解决此类任务的最佳方法是使用 Spring Data JPA Specifications(和 JPA Criteria API)。例如:

1) 让我们为我们的实体Model 创建一个实现SpecificationFilter 类:

@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) 然后创建一个控制器方法:

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

剩下要做的就是准备我们的谓词))

为此,我们可以先创建一个方便的“谓词生成器”界面:

@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);
    

然后试着做我们的谓词:

相等

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();
    
;

喜欢

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();
    
;

开始

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();
    
;

之间

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();
    
;

来自

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();
    
;

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();
    
;

剩下的只是完成Filter类:

@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]));
    

当然,这只是实现的一个示例。您可以创建自己需要的规范和谓词的实现。这里的主要内容是:

想出你的搜索界面 制定您的“过滤器”规范 准备好所有你需要的谓词 在控制器方法中使用过滤器规范

【讨论】:

我有一个问题。你知道有没有其他搜索框架可以用来更轻松地实现上述功能? 通常在复杂项目中如何实现搜索逻辑? @PeterPenzov 你是什么意思? 如果您对某些数据库表有一个复杂的搜索过滤器,以便像上面的示例一样进行复杂的查询,您会使用什么? @PeterPenzov 我认为您可以选择所需的每个选项:***.com/a/55761257

以上是关于使用条件实现搜索过滤器的主要内容,如果未能解决你的问题,请参考以下文章

使用全文搜索和其他条件搜索1300万条记录

在MongoDB中返回数据之前有条件地添加过滤器困境

Dexie JS - 结合条件过滤器和文本搜索

如何在vue js的过滤器搜索中添加大于等于的条件

过滤数据

数据过滤--高级搜索条件查询数据