Langage de requête REST - Implémentation de l'opération OR

Haut REST

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS Cet article fait partie d'une série: • Langage de requête REST avec critères Spring et JPA

• Langage de requête REST avec spécifications Spring Data JPA

• Langage de requête REST avec Spring Data JPA et Querydsl

• REST Query Language - Opérations de recherche avancées

• REST Query Language - Implémentation de l'opération OR (article actuel) • REST Query Language avec RSQL

• Langage de requête REST avec support Web Querydsl

1. Vue d'ensemble

Dans cet article rapide, nous allons étendre les opérations de recherche avancée que nous avons implémentées dans l'article précédent et inclure des critères de recherche basés sur OR dans notre langage de requête API REST .

2. Approche de mise en œuvre

Auparavant, tous les critères du paramètre de requête de recherche formaient des prédicats regroupés uniquement par l'opérateur AND. Changeons cela.

Nous devrions être en mesure de mettre en œuvre cette fonctionnalité soit en tant que changement simple et rapide d'une approche existante, soit en une nouvelle à partir de zéro.

Avec l'approche simple, nous marquerons les critères pour indiquer qu'ils doivent être combinés à l'aide de l'opérateur OR.

Par exemple, voici l'URL pour tester l'API pour " firstName OR lastName":

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

Notez que nous avons marqué le critère lastName avec un guillemet simple pour le différencier. Nous capturerons ce prédicat pour l'opérateur OR dans notre objet de valeur de critère - 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. Amélioration de UserSpecificationBuilder

Maintenant, modifions notre générateur de spécifications, UserSpecificationBuilder, pour prendre en compte les critères qualifiés OR lors de la construction de la spécification :

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. Amélioration de UserController

Enfin, configurons un nouveau point de terminaison REST dans notre contrôleur pour utiliser cette fonctionnalité de recherche avec l'opérateur OR. La logique d'analyse améliorée extrait l'indicateur spécial qui aide à identifier les critères avec l'opérateur 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 en direct avec condition OU

Dans cet exemple de test en direct, avec le nouveau point de terminaison d'API, nous rechercherons les utilisateurs par le prénom «john» OU le nom de famille «doe». Notez que le paramètre lastName a un guillemet simple, ce qui le qualifie de «prédicat 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 de persistance avec condition OU

Maintenant, effectuons le même test que nous avons fait ci-dessus, au niveau de la persistance pour les utilisateurs avec le prénom «john» OU le nom de famille «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. Approche alternative

Dans l'approche alternative, nous pourrions fournir la requête de recherche plus comme une clause WHERE complète d'une requête SQL.

Par exemple, voici l'URL pour une recherche plus complexe par prénom et âge:

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

Notez que nous avons séparé les critères individuels, les opérateurs et les parenthèses de regroupement avec un espace pour former une expression d'infixe valide.

Analysons l'expression infixe avec un CriteriaParser . Notre CriteriaParser divise l'expression d'infixe donnée en jetons (critères, parenthèses, opérateurs AND & OR) et crée une expression de suffixe pour la même:

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

Ajoutons une nouvelle méthode dans notre générateur de spécifications, GenericSpecificationBuilder, pour construire la spécification de recherche à partir de l'expression postfix:

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

Enfin, ajoutons un autre point de terminaison REST dans notre UserController pour analyser l'expression complexe avec le nouveau 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. Conclusion

Dans ce didacticiel, nous avons amélioré notre langage de requête REST avec la possibilité de rechercher avec un opérateur OR.

L'implémentation complète de cet article se trouve dans le projet GitHub. Il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.

Suivant » Langage de requête REST avec RSQL « Précédent Langage de requête REST - Opérations de recherche avancées REST bottom

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS