REST Query Language - Implementazione dell'operazione OR

REST Top

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> GUARDA IL CORSO Questo articolo fa parte di una serie: • REST Query Language con criteri Spring e JPA

• Linguaggio di query REST con specifiche JPA Spring Data

• REST Query Language con Spring Data JPA e Querydsl

• REST Query Language - Operazioni di ricerca avanzate

• REST Query Language - Implementazione dell'operazione OR (articolo corrente) • REST Query Language con RSQL

• Linguaggio query REST con supporto Web Querydsl

1. Panoramica

In questo rapido articolo, estenderemo le operazioni di ricerca avanzata che abbiamo implementato nell'articolo precedente e includeremo criteri di ricerca basati su OR nel nostro linguaggio di query dell'API REST .

2. Approccio all'implementazione

Prima, tutti i criteri nel parametro della query di ricerca formavano predicati raggruppati solo per operatore AND. Cambiamolo.

Dovremmo essere in grado di implementare questa funzione come un semplice e rapido cambiamento all'approccio esistente o uno nuovo da zero.

Con l'approccio semplice, contrassegneremo i criteri per indicare che deve essere combinato utilizzando l'operatore OR.

Ad esempio, ecco l'URL per testare l'API per " firstName OR lastName":

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

Nota che abbiamo contrassegnato i criteri lastName con una singola virgoletta per differenziarlo. Cattureremo questo predicato per l'operatore OR nel nostro oggetto valore criteri - 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. Miglioramento di UserSpecificationBuilder

Ora, modifichiamo il nostro generatore di specifiche, UserSpecificationBuilder, per considerare i criteri qualificati OR durante la costruzione della specifica :

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. Miglioramento UserController

Infine, configuriamo un nuovo endpoint REST nel nostro controller per utilizzare questa funzionalità di ricerca con l'operatore OR. La logica di analisi migliorata estrae il flag speciale che aiuta a identificare i criteri con l'operatore 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. Test dal vivo con condizione OR

In questo esempio di test dal vivo, con il nuovo endpoint API, cercheremo gli utenti con il nome "john" O il cognome "doe". Tieni presente che il parametro lastName ha una virgoletta singola, che lo qualifica come "predicato 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. Test di persistenza con condizione OR

Ora, eseguiamo lo stesso test che abbiamo fatto sopra, a livello di persistenza per gli utenti con nome "john" O cognome "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. Approccio alternativo

Nell'approccio alternativo, potremmo fornire la query di ricerca più simile a una clausola WHERE completa della query SQL.

Ad esempio, qui è l'URL per una ricerca più complessa da firstName e l'età:

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

Nota che abbiamo separato i singoli criteri, operatori e parentesi di raggruppamento con uno spazio per formare un'espressione infissa valida.

Analizziamo l'espressione infix con un CriteriaParser . Il nostro CriteriaParser divide l'espressione infissa data in token (criteri, parentesi, operatori AND e OR) e crea un'espressione postfissa per lo stesso:

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; }

Aggiungiamo un nuovo metodo nel nostro generatore di specifiche, GenericSpecificationBuilder, per costruire la specifica di ricerca dall'espressione postfissa:

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

Infine, aggiungiamo un altro endpoint REST nel nostro UserController per analizzare l'espressione complessa con il nuovo 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. Conclusione

In questo tutorial, abbiamo migliorato il nostro linguaggio di query REST con la capacità di cercare con un operatore OR.

L'implementazione completa di questo articolo può essere trovata nel progetto GitHub. Questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.

Successivo » REST Query Language con RSQL « Precedente REST Query Language - Operazioni di ricerca avanzate REST bottom

Ho appena annunciato il nuovo corso Learn Spring , incentrato sui fondamenti di Spring 5 e Spring Boot 2:

>> SCOPRI IL CORSO