REST Query Language – Implementing OR Operation – REST查询语言 – 实现OR操作

最后修改: 2017年 4月 10日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick article, we’ll extend the advanced search operations that we implemented in the previous article and include OR-based search criteria into our REST API Query Language.

在这篇快速文章中,我们将扩展我们在前一篇文章中实现的高级搜索操作,并将基于OR的搜索条件纳入我们的REST API查询语言

2. Implementation Approach

2.实施方法

Before, all the criteria in the search query parameter formed predicates grouped only by AND operator. Let’s change that.

以前,search查询参数中的所有标准只由AND操作符组成谓词。让我们来改变一下。

We should be able to implement this feature either as a simple, quick change to existing approach or a new one from scratch.

我们应该能够实现这一功能,要么对现有的方法进行简单、快速的改变,要么从头开始实施一个新的方法。

With the simple approach, we’ll flag the criteria to indicate that it must be combined using the OR operator.

用简单的方法,我们会对标准进行标记,表示必须用OR运算符进行组合。

For example, here is the URL to test the API for “firstName OR lastName”:

例如,这里是测试”firstName OR lastName “的API的URL:

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

Note that we have flagged the criteria lastName with a single quote to differentiate it. We will capture this predicate for OR operator in our criteria value object – SpecSearchCriteria:

注意,我们已经用单引号标记了标准lastName,以区别它。我们将在我们的标准值对象–SpecSearchCriteria:中捕获这个OR操作的谓词。

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 Improvement

3.UserSpecificationBuilder改进

Now, let’s modify our specification builder, UserSpecificationBuilder, to consider the OR qualified criteria when constructing Specification<User>:

现在,让我们修改我们的规范生成器,UserSpecificationBuilder,在构建Specification<User>时考虑OR限定条件。

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 Improvement

4.UserController改进

Finally, let’s set up a new REST endpoint in our controller to use this search functionality with OR operator. The improved parsing logic extracts the special flag that helps in identifying the criteria with OR operator:

最后,让我们在控制器中设置一个新的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. Live Test With OR Condition

5.有OR条件的现场测试

In this live test example, with the new API endpoint, we’ll search for users by the first name “john” OR last name “doe”. Note that parameter lastName has a single quote, which qualifies it as an “OR predicate”:

在这个实时测试的例子中,使用新的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. Persistence Test With OR Condition

6.带有OR条件的持久性测试

Now, let’s perform the same test we did above, at the persistence level for users with first name “john” OR last name “doe”:

现在,让我们执行上面的测试,在持久化级别对用户名字为 “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. Alternative Approach

7.替代方法

In the alternate approach, we could provide the search query more like a complete WHERE clause of SQL query.

在另一种方法中,我们可以提供搜索查询,更像是SQL查询的一个完整的WHERE条款。

For example, here is the URL for a more complex search by firstName and age:

例如,这里是按firstNameage:进行更复杂搜索的URL。

http://localhost:8080/users?search=( firstName:john OR firstName:tom ) AND age>22

Note that we have separated individual criteria, operators & grouping parenthesis with a space to form a valid infix expression.

请注意,我们已经用空格分隔了各个标准、运算符和分组小括号,以形成一个有效的infix表达式。

Let us parse the infix expression with a CriteriaParser. Our CriteriaParser splits the given infix expression into tokens (criteria, parenthesis, AND & OR operators) and creates a postfix expression for the same:

让我们用CriteriaParser来解析这个infix表达式。我们的CriteriaParser将给定的infix表达式分割成标记(标准、小括号、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;
}

Let us add a new method in our specification builder, GenericSpecificationBuilder, to construct the search Specification from the postfix expression:

让我们在我们的规范构建器中添加一个新的方法,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();

Finally, let us add another REST endpoint in our UserController to parse the complex expression with the new CriteriaParser:

最后,让我们在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. Conclusion

8.结论

In this tutorial, we’ve improved our REST query language with the capability to search with an OR operator.

在本教程中,我们改进了我们的REST查询语言,使其具有用OR运算符进行搜索的能力。

The full implementation of this article can be found in the GitHub project. This is a Maven-based project, so it should be easy to import and run as it is.

本文的完整实现可以在GitHub项目中找到。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。

Next »

REST Query Language with RSQL

« Previous

REST Query Language – Advanced Search Operations