REST 쿼리 언어 – OR 작업 구현

REST 상단

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인 이 기사는 시리즈의 일부입니다. • Spring 및 JPA 기준을 사용하는 REST 쿼리 언어

• SpringData JPA 사양을 사용한 REST 쿼리 언어

• SpringData JPA 및 Querydsl을 사용한 REST 쿼리 언어

• REST 쿼리 언어 – 고급 검색 작업

• REST 쿼리 언어 – OR 연산 구현 (현재 기사) • RSQL을 사용한 REST 쿼리 언어

• Querydsl 웹 지원이 포함 된 REST 쿼리 언어

1. 개요

이 빠른 기사에서는 이전 기사에서 구현 한 고급 검색 작업을 확장하고 OR 기반 검색 기준을 REST API 쿼리 언어에 포함 합니다.

2. 구현 접근법

이전에는 검색 쿼리 매개 변수 의 모든 기준 이 AND 연산자로만 그룹화 된 술어를 형성했습니다. 그것을 바꾸자.

이 기능은 기존 접근 방식에 대한 간단하고 빠른 변경 또는 처음부터 새로운 접근 방식으로 구현할 수 있어야합니다.

간단한 접근 방식으로 OR 연산자를 사용하여 조합해야 함을 나타내는 기준에 플래그를 지정합니다.

예를 들어 다음은 " firstName OR lastName"에 대한 API를 테스트하는 URL입니다 .

//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 개선

이제 사양 작성기 인 UserSpecificationBuilder를 수정 하여 사양을 구성 할 때 OR 자격 기준을 고려해 보겠습니다 .

public Specification build() { if (params.size() == 0) { return null; } Specification 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 findAllByOrPredicate(@RequestParam String search) { Specification spec = resolveSpecification(search); return dao.findAll(spec); } protected Specification 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 = "//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 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연령별 로 더 복잡한 검색을위한 URL입니다 .

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

유효한 중위 식을 형성하기 위해 개별 기준, 연산자 및 그룹화 괄호를 공백으로 구분했습니다.

CriteriaParser로 중위 표현식을 구문 분석해 보겠습니다 . 우리 CriteriaParser은 (AND 및 OR 연산자, 기준, 괄호) 토큰에 지정된 중위 식을 분할하고 같은에 대한 후위 식을 생성합니다 :

public Deque parse(String searchParam) { Deque output = new LinkedList(); Deque 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 에 새 메소드를 추가 하여 접미사 표현식에서 검색 사양 을 구성 해 보겠습니다 .

 public Specification build(Deque postFixedExprStack, Function
    
      converter) { Deque
     
       specStack = new LinkedList(); while (!postFixedExprStack.isEmpty()) { Object mayBeOperand = postFixedExprStack.pollLast(); if (!(mayBeOperand instanceof String)) { specStack.push(converter.apply((SpecSearchCriteria) mayBeOperand)); } else { Specification operand1 = specStack.pop(); Specification 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 findAllByAdvPredicate(@RequestParam String search) { Specification spec = resolveSpecificationFromInfixExpr(search); return dao.findAll(spec); } protected Specification resolveSpecificationFromInfixExpr(String searchParameters) { CriteriaParser parser = new CriteriaParser(); GenericSpecificationsBuilder specBuilder = new GenericSpecificationsBuilder(); return specBuilder.build(parser.parse(searchParameters), UserSpecification::new); }

8. 결론

이 자습서에서는 OR 연산자로 검색하는 기능을 사용하여 REST 쿼리 언어를 개선했습니다.

이 기사의 전체 구현은 GitHub 프로젝트에서 찾을 수 있습니다. 이것은 Maven 기반 프로젝트이므로 그대로 가져 와서 실행하기 쉽습니다.

다음 » RSQL을 사용한 REST 쿼리 언어 « 이전 REST 쿼리 언어 – 고급 검색 작업 REST 하단

방금 Spring 5 및 Spring Boot 2의 기본 사항에 초점을 맞춘 새로운 Learn Spring 과정을 발표했습니다 .

>> 과정 확인