REST 查詢語言 – 實現 OR 運算

REST,Spring
Remote
1
12:46 PM · Dec 01 ,2025

1. 概述

在本文檔中,我們將擴展我們先前文章中實現的複雜搜索操作,並將 基於 OR 的搜索條件納入我們的 REST API 查詢語言

2. 實現方法此前,所有在 搜索 查詢參數中的條件都只通過 AND 運算符進行分組。我們應該改變這一點。

我們應該能夠通過簡單的快速更改現有方法或從頭開始的新方法來實現此功能。

通過簡單的方案,我們將標記條件以指示必須使用 OR 運算符進行組合。

例如,以下是用於“firstName OR lastName”測試 API 的 URL:

http://localhost:8080/users?search=firstName:john,'lastName:doe

請注意,我們已標記條件 lastName 為單引號,以區分它們。我們將捕獲此謂詞作為 OR 運算符的條件值對象——SpecSearchCriteria:

public SpecSearchCriteria(
  String orPredicate, String key, SearchOperation operation, Object value) {
    super();
    
    this.orPredicate 
      = orPredicate != null
      && orPredicate.equals(SearchOperation.OR_PREDICATE_FLAG);
    
    this.key = key;
    this.operation = operation;
    this.value = value;
}

3. UserSpecificationBuilder 改進現在,讓我們修改我們的規範構建器,UserSpecificationBuilder,以考慮 OR 類型的條件時構造 Specification<User>:

public Specification<User> build() {
    if (params.size() == 0) {
        return null;
    }
    Specification<User> result = new UserSpecification(params.get(0));

    for (int i = 1; i < params.size(); i++) {
        result = params.get(i).isOrPredicate()
          ? Specification.where(result).or(new UserSpecification(params.get(i))) 
          : Specification.where(result).and(new UserSpecification(params.get(i)));
    }
    return result;
 }

4. UserController 改進

最後,我們將在控制器的新 REST 端點中使用此搜索功能,並使用 OR 運算符。改進的解析邏輯提取了有助於識別具有 OR 運算符的特殊標誌:

@GetMapping("/users/espec")
@ResponseBody
public List<User> findAllByOrPredicate(@RequestParam String search) {
    Specification<User> spec = resolveSpecification(search);
    return dao.findAll(spec);
}

protected Specification<User> resolveSpecification(String searchParameters) {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();
    String operationSetExper = Joiner.on("|")
      .join(SearchOperation.SIMPLE_OPERATION_SET);
    Pattern pattern = Pattern.compile(
      "(\\p{Punct}?)(\\w+?)("
      + operationSetExper 
      + ")(\\p{Punct}?)(\\w+?)(\\p{Punct}?),");
    Matcher matcher = pattern.matcher(searchParameters + ",");
    while (matcher.find()) {
        builder.with(matcher.group(1), matcher.group(2), matcher.group(3), 
        matcher.group(5), matcher.group(4), matcher.group(6));
    }
    
    return builder.build();
}

5. 實時測試中使用 OR 條件

在本次實時測試示例中,使用新的 API 端點,我們將按第一個名字“john”或最後一個名字“doe”搜索用户。請注意,參數 lastName 包含單引號,這使其成為“OR 謂詞”:

private String EURL_PREFIX
  = "http://localhost:8082/spring-rest-full/auth/users/espec?search=";

@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    Response response = givenAuth().get(EURL_PREFIX + "firstName:john,'lastName:doe");
    String result = response.body().asString();

    assertTrue(result.contains(userJohn.getEmail()));
    assertTrue(result.contains(userTom.getEmail()));
}

6. 使用 OR 條件進行持久性測試

現在,我們執行與之前相同的一樣持久性級別測試,針對用户名 為“john” 或 “doe” 的用户:

@Test
public void givenFirstOrLastName_whenGettingListOfUsers_thenCorrect() {
    UserSpecificationsBuilder builder = new UserSpecificationsBuilder();

    SpecSearchCriteria spec 
      = new SpecSearchCriteria("firstName", SearchOperation.EQUALITY, "john");
    SpecSearchCriteria spec1 
      = new SpecSearchCriteria("'","lastName", SearchOperation.EQUALITY, "doe");

    List<User> results = repository
      .findAll(builder.with(spec).with(spec1).build());

    assertThat(results, hasSize(2));
    assertThat(userJohn, isIn(results));
    assertThat(userTom, isIn(results));
}

7. 替代方案

在替代方案中,我們可以更像一個完整的 WHERE 子句提供搜索查詢。

例如,以下是使用 firstNameage 進行更復雜搜索的 URL:

http://localhost:8082/spring-rest-query-language/auth/users?search=( firstName:john OR firstName:tom ) AND age>22

請注意,我們已將單個標準、運算符和分組括號與空格分隔,以形成有效的前綴表達式。

讓我們使用 CriteriaParser 解析前綴表達式。我們的 CriteriaParser 將給定的前綴表達式分解為標記(標準、括號、AND 和 OR 運算符)併為之創建後綴表達式:

public Deque<?> parse(String searchParam) {

    Deque<Object> output = new LinkedList<>();
    Deque<String> stack = new LinkedList<>();

    Arrays.stream(searchParam.split("\\s+")).forEach(token -> {
        if (ops.containsKey(token)) {
            while (!stack.isEmpty() && isHigerPrecedenceOperator(token, stack.peek())) {
                output.push(stack.pop().equalsIgnoreCase(SearchOperation.OR_OPERATOR)
                  ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);
            }
            stack.push(token.equalsIgnoreCase(SearchOperation.OR_OPERATOR) 
              ? SearchOperation.OR_OPERATOR : SearchOperation.AND_OPERATOR);

        } else if (token.equals(SearchOperation.LEFT_PARANTHESIS)) {
            stack.push(SearchOperation.LEFT_PARANTHESIS);
        } else if (token.equals(SearchOperation.RIGHT_PARANTHESIS)) {
            while (!stack.peek().equals(SearchOperation.LEFT_PARANTHESIS)) { 
                output.push(stack.pop());
            }
            stack.pop();
        } else {
            Matcher matcher = SpecCriteraRegex.matcher(token);
            while (matcher.find()) {
                output.push(new SpecSearchCriteria(
                  matcher.group(1), 
                  matcher.group(2), 
                  matcher.group(3), 
                  matcher.group(4), 
                  matcher.group(5)));
            }
        }
    });

    while (!stack.isEmpty()) {
        output.push(stack.pop());
    }
  
    return output;}

讓我們在我們的規範構建器 GenericSpecificationBuilder 中添加一個新的方法,用於從後綴表達式構建搜索 Specification

    public Specification<U> build(Deque<?> postFixedExprStack, 
        Function<SpecSearchCriteria, Specification<U>> converter) {

        Deque<Specification<U>> specStack = new LinkedList<>();

        while (!postFixedExprStack.isEmpty()) {
            Object mayBeOperand = postFixedExprStack.pollLast();

            if (!(mayBeOperand instanceof String)) {
                specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand));
            } else {
                Specification<U> operand1 = specStack.pop();
                Specification<U> operand2 = specStack.pop();
                if (mayBeOperand.equals(SearchOperation.AND_OPERATOR)) {
                    specStack.push(Specification.where(operand1)
                      .and(operand2));
                }
                else if (mayBeOperand.equals(SearchOperation.OR_OPERATOR)) {
                    specStack.push(Specification.where(operand1)
                      .or(operand2));
                }
            }
        }
        return specStack.pop();

最後,讓我們在我們的 UserController 中添加另一個 REST 端點,用於使用新的 CriteriaParser 解析複雜的表達式:

@GetMapping("/users/spec/adv")
@ResponseBody
public List<User> findAllByAdvPredicate(@RequestParam String search) {
    Specification<User> spec = resolveSpecificationFromInfixExpr(search);
    return dao.findAll(spec);
}

protected Specification<User> resolveSpecificationFromInfixExpr(String searchParameters) {
    CriteriaParser parser = new CriteriaParser();
    GenericSpecificationsBuilder<User> specBuilder = new GenericSpecificationsBuilder<>();
    return specBuilder.build(parser.parse(searchParameters), UserSpecification::new);
}

8. 結論

在本教程中,我們已經改進了 REST 查詢語言,使其具備使用 OR 運算符進行搜索的功能。

下一條 »
REST Query Language with RSQL
« 之前的
REST Query Language – Advanced Search Operations
user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.