Linguagem de Consulta REST - Implementando OU Operação
1. Visão geral
Neste artigo rápido, vamos estender as operações de pesquisa avançada que implementamos emprevious article e incluirOR-based search criteria into our REST API Query Language.
2. Abordagem de Implementação
Antes, todos os critérios no parâmetro de consultasearch formavam predicados agrupados apenas pelo operador AND. Vamos mudar isso.
Deveríamos ser capazes de implementar esse recurso como uma alteração simples e rápida da abordagem existente ou uma nova do zero.
Com a abordagem simples, sinalizaremos os critérios para indicar que devem ser combinados usando o operador OR.
Por exemplo, aqui está o URL para testar a API para “firstName OR lastName”:
http://localhost:8080/users?search=firstName:john,'lastName:doe
Observe que sinalizamos os critérioslastName com uma aspa simples para diferenciá-los. Capturaremos este predicado para o operador OR em nosso objeto de valor de critérios -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. UserSpecificationBuilder Melhoria
Agora, vamos modificar nosso construtor de especificações,UserSpecificationBuilder, para considerar os critérios qualificados OR ao construirSpecification<User>:
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 Melhoria
Finalmente, vamos configurar um novo endpoint REST em nosso controlador para usar esta funcionalidade de pesquisa com o operador OR. A lógica de análise aprimorada extrai o sinalizador especial que ajuda a identificar os critérios com o operador 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. Teste ao vivo com condiçãoOR
Neste exemplo de teste ao vivo, com o novo endpoint da API, pesquisaremos usuários pelo primeiro nome “john” OU pelo sobrenome “doe”. Observe que o parâmetrolastName tem aspas simples, o que o qualifica como um “predicado 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. Teste de persistência com condiçãoOR
Agora, vamos realizar o mesmo teste que fizemos acima, no nível de persistência para usuárioswith first name “john” OR last name “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. Abordagem alternativa
Na abordagem alternativa, poderíamos fornecer a consulta de pesquisa mais como uma cláusulaWHERE completa da consulta SQL.
Por exemplo, aqui está o URL para uma pesquisa mais complexa porfirstNameeage:
http://localhost:8080/users?search=( firstName:john OR firstName:tom ) AND age>22
Observe que separamos critérios individuais, operadores e parênteses de agrupamento com um espaço para formar uma expressão de infixo válida.
Vamos analisar a expressão infixa com umCriteriaParser. NossoCriteriaParser divide a expressão infixada fornecida em tokens (critérios, parênteses, operadores AND e OR) e cria uma expressão pós-fixada para o mesmo:
public Deque> parse(String searchParam) {
Deque
Vamos adicionar um novo método em nosso construtor de especificações,GenericSpecificationBuilder, para construir a pesquisaSpecification a partir da expressão pós-fixada:
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();
Finalmente, vamos adicionar outro ponto final REST em nossoUserController para analisar a expressão complexa com o novoCriteriaParser:
@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. Conclusão
Neste tutorial, aprimoramos nossa linguagem de consulta REST com a capacidade de pesquisar com um operador OR.
A implementação completa deste artigo pode ser encontrada emthe GitHub project. Este é um projeto baseado em Maven, portanto, deve ser fácil importar e executar como está.