Consultas do Elasticsearch com dados do Spring
1. Introdução
Em aprevious article, demonstramos como configurar e usar Spring Data Elasticsearch para um projeto. Neste artigo, examinaremos vários tipos de consulta oferecidos pelo Elasticsearch e também falaremos sobre analisadores de campo e seu impacto nos resultados da pesquisa.
2. Analisadores
Todos os campos de sequência armazenados são, por padrão, processados por um analisador. Um analisador consiste em um tokenizador e vários filtros de token e geralmente é precedido por um ou mais filtros de caracteres.
O analisador padrão divide a string por separadores de palavras comuns (como espaços ou pontuação) e coloca todos os tokens em minúsculas. Ele também ignora palavras comuns em inglês.
O Elasticsearch também pode ser configurado para considerar um campo como analisado e não analisado ao mesmo tempo.
Por exemplo, em uma classeArticle, suponha que armazenemos o campo de título como um campo padrão analisado. O mesmo campo com o sufixoverbatim será armazenado como um campo não analisado:
@MultiField(
mainField = @Field(type = Text, fielddata = true),
otherFields = {
@InnerField(suffix = "verbatim", type = Keyword)
}
)
private String title;
Aqui, aplicamos a anotação@MultiField para informar ao Spring Data que gostaríamos que este campo fosse indexado de várias maneiras. O campo principal usará o nometitlee será analisado de acordo com as regras descritas acima.
Mas também fornecemos uma segunda anotação,@InnerField, que descreve uma indexação adicional do campotitle. UsamosFieldType.keyword para indicar que não queremos usar um analisador ao realizar a indexação adicional do campo, e que esse valor deve ser armazenado usando um campo aninhado com o sufixoverbatim.
2.1. Campos Analisados
Vamos ver um exemplo. Suponha que um artigo com o título "Spring Data Elasticsearch" seja adicionado ao nosso índice. O analisador padrão dividirá a string nos caracteres de espaço e produzirá tokens em minúsculas: “spring“, “dados“, e“elasticsearch “.
Agora, podemos usar qualquer combinação desses termos para corresponder a um documento:
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "elasticsearch data"))
.build();
2.2. Campos não analisados
Um campo não analisado não é tokenizado; portanto, só pode ser correspondido como um todo ao usar consultas de correspondência ou termo:
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title.verbatim", "Second Article About Elasticsearch"))
.build();
Usando uma consulta de correspondência, podemos pesquisar apenas pelo título completo, que também diferencia maiúsculas de minúsculas.
3. Consulta de correspondência
Amatch query aceita texto, números e datas.
Existem três tipos de consulta de "correspondência":
-
boleano
-
phrase e
-
frase_prefixo
Nesta seção, exploraremos a consulta de correspondênciaboolean.
3.1. Correspondência com operadores booleanos
boolean é o tipo padrão de uma consulta de correspondência; você pode especificar qual operador booleano usar (or é o padrão):
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title","Search engines").operator(AND))
.build();
List articles = getElasticsearchTemplate()
.queryForList(searchQuery, Article.class);
Esta consulta retornaria um artigo com o título “Mecanismos de pesquisa”, especificando dois termos do título com o operadorand. Mas o que acontecerá se pesquisarmos com o operador padrão (or) quando apenas um dos termos corresponder?
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "Engines Solutions"))
.build();
List articles = getElasticsearchTemplate()
.queryForList(searchQuery, Article.class);
assertEquals(1, articles.size());
assertEquals("Search engines", articles.get(0).getTitle());
O artigo “Search engines” ainda é compatível, mas terá uma pontuação mais baixa porque nem todos os termos corresponderam.
A soma das pontuações de cada termo correspondente soma a pontuação total de cada documento resultante.
Pode haver situações em que um documento contendo um termo raro digitado na consulta tenha uma classificação mais alta que um documento que contém vários termos comuns.
3.2. Imprecisão
Quando o usuário comete um erro de digitação em uma palavra, ainda é possível combiná-lo com uma pesquisa especificando um parâmetrofuzziness, que permite uma correspondência inexata.
Para campos de stringfuzziness significa a distância de edição: o número de alterações de um caractere que precisam ser feitas em uma string para torná-la igual a outra string.
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchQuery("title", "spring date elasticsearch")
.operator(AND)
.fuzziness(Fuzziness.ONE)
.prefixLength(3))
.build();
O parâmetroprefix_length é usado para melhorar o desempenho. Nesse caso, exigimos que os três primeiros caracteres correspondam exatamente, o que reduz o número de combinações possíveis.
5. Pesquisa de frase
A pesquisa de fase é mais restrita, embora você possa controlá-la com o parâmetroslop. Este parâmetro informa à frase consulta a que distância os termos são permitidos enquanto ainda considera o documento uma correspondência.
Em outras palavras, representa o número de vezes que você precisa mover um termo para fazer a consulta e o documento corresponderem:
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(matchPhraseQuery("title", "spring elasticsearch").slop(1))
.build();
Aqui, a consulta irá corresponder ao documento com o título “Spring Data Elasticsearch” porque definimos o slop como um.
6. Consulta de correspondência múltipla
Quando você deseja pesquisar em vários campos, você pode usarQueryBuilders#multiMatchQuery() onde especifica todos os campos para corresponder:
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(multiMatchQuery("tutorial")
.field("title")
.field("tags")
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS))
.build();
Aqui, pesquisamos os campostitleetags por uma correspondência.
Observe que aqui usamos a estratégia de pontuação dos "melhores campos". Ele levará a pontuação máxima entre os campos como pontuação do documento.
7. Agregações
Em nossa classeArticle, também definimos um campotags, que não é analisado. Poderíamos facilmente criar uma nuvem de tags usando uma agregação.
Lembre-se de que, como o campo não é analisado, as tags não serão tokenizadas:
TermsAggregationBuilder aggregation = AggregationBuilders.terms("top_tags")
.field("tags")
.order(Terms.Order.count(false));
SearchResponse response = client.prepareSearch("blog")
.setTypes("article")
.addAggregation(aggregation)
.execute().actionGet();
Map results = response.getAggregations().asMap();
StringTerms topTags = (StringTerms) results.get("top_tags");
List keys = topTags.getBuckets()
.stream()
.map(b -> b.getKeyAsString())
.collect(toList());
assertEquals(asList("elasticsearch", "spring data", "search engines", "tutorial"), keys);
8. Sumário
Neste artigo, discutimos a diferença entre campos analisados e não analisados e como essa distinção afeta a pesquisa.
Também aprendemos sobre vários tipos de consultas fornecidas pelo Elasticsearch, como consulta de correspondência, consulta de correspondência de frase, consulta de pesquisa de texto completo e consulta booleana.
O Elasticsearch fornece muitos outros tipos de consultas, como consultas geográficas, consultas de script e consultas compostas. Você pode ler sobre eles emElasticsearch documentatione explorar a API Spring Data Elasticsearch para usar essas consultas em seu código.
Você pode encontrar um projeto contendo os exemplos usados neste artigo emthe GitHub repository.