RESTクエリ言語 - OR演算の実装

1概要

このクイック記事では、/rest-api-query-search-language-more-operations[前の記事]のリンクに実装した高度な検索操作を拡張し、 ORベースの検索条件をREST APIに含めます。クエリ言語

2実施アプローチ

以前は、 search queryパラメーター内のすべての基準は、AND演算子によってのみグループ化された述部を形成していました。それを変えましょう。

この機能は、既存のアプローチに対する単純で迅速な変更として、または最初から新しいアプローチとして実装することができます。

単純なアプローチでは、OR演算子を使用して結合する必要があることを示すために基準にフラグを立てます。

たとえば、「** firstName OR lastName」のAPIをテストするためのURLは次のとおりです。

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

区別するために、基準 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 改善

それでは、 Specification <User> を作成するときにOR修飾基準を考慮するように、仕様ビルダー__UserSpecificationBuilderを変更しましょう。

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 改善

最後に、この検索機能をOR演算子で使用するために、コントローラーに新しいRESTエンドポイントを設定しましょう。改良された構文解析ロジックは、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 は一重引用符で囲まれていることに注意してください。

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. 代替アプローチ

別の方法では、SQLクエリの完全な WHERE 句のような検索クエリを提供できます。

たとえば、 firstName と__ageによるさらに複雑な検索のURLは次のとおりです。

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

我々は個々の基準、演算子を分離していることに注意してください

CriteriaParser を使用して中置式を解析しましょう。私たちの CriteriaParser は与えられた中置式をトークン(基準、かっこ、AND)に分割します。

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結論

このチュートリアルでは、OR演算子で検索する機能を使用してRESTクエリ言語を改善しました。

この記事の完全な実装はhttps://github.com/eugenp/tutorials/tree/master/spring-rest-query-language[GitHubプロジェクト]にあります。これはMavenベースのプロジェクトなので、そのままインポートして実行するのは簡単なはずです。

"

  • «** 前へ