Une implémentation de balisage simple avec Elasticsearch

Haut de persistance

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: • Une implémentation de balisage simple avec Elasticsearch (article actuel) • Une implémentation de balisage simple avec JPA

• Une implémentation de balisage avancée avec JPA

• Une implémentation de balisage simple avec MongoDB

1. Vue d'ensemble

Le balisage est un modèle de conception courant qui nous permet de catégoriser et de filtrer les éléments de notre modèle de données.

Dans cet article, nous allons implémenter le balisage à l'aide de Spring et Elasticsearch. Nous utiliserons à la fois Spring Data et l'API Elasticsearch.

Tout d'abord, nous n'allons pas couvrir les bases de l'obtention d'Elasticsearch et de Spring Data - vous pouvez les explorer ici.

2. Ajout de balises

L'implémentation la plus simple du balisage est un tableau de chaînes. Nous pouvons l'implémenter en ajoutant un nouveau champ à notre modèle de données comme ceci:

@Document(indexName = "blog", type = "article") public class Article { // ... @Field(type = Keyword) private String[] tags; // ... }

Notez l'utilisation du type de champ Mot - clé . Nous voulons uniquement des correspondances exactes de nos balises pour filtrer un résultat. Cela nous permet d'utiliser des balises similaires mais séparées comme elasticsearchIsAwesome et elasticsearchIsTerrible .

Les champs analysés renverraient des résultats partiels, ce qui est un comportement incorrect dans ce cas.

3. Création de requêtes

Les balises nous permettent de manipuler nos requêtes de manière intéressante. Nous pouvons les rechercher comme n'importe quel autre champ, ou nous pouvons les utiliser pour filtrer nos résultats sur les requêtes match_all . Nous pouvons également les utiliser avec d'autres requêtes pour renforcer nos résultats.

3.1. Recherche de tags

Le nouveau champ de balise que nous avons créé sur notre modèle est comme tous les autres champs de notre index. Nous pouvons rechercher n'importe quelle entité qui a une balise spécifique comme celle-ci:

@Query("{\"bool\": {\"must\": [{\"match\": {\"tags\": \"?0\"}}]}}") Page findByTagUsingDeclaredQuery(String tag, Pageable pageable);

Cet exemple utilise un référentiel de données Spring pour construire notre requête, mais nous pouvons tout aussi rapidement utiliser un modèle de repos pour interroger manuellement le cluster Elasticsearch.

De même, nous pouvons utiliser l'API Elasticsearch:

boolQuery().must(termQuery("tags", "elasticsearch"));

Supposons que nous utilisons les documents suivants dans notre index:

[ { "id": 1, "title": "Spring Data Elasticsearch", "authors": [ { "name": "John Doe" }, { "name": "John Smith" } ], "tags": [ "elasticsearch", "spring data" ] }, { "id": 2, "title": "Search engines", "authors": [ { "name": "John Doe" } ], "tags": [ "search engines", "tutorial" ] }, { "id": 3, "title": "Second Article About Elasticsearch", "authors": [ { "name": "John Smith" } ], "tags": [ "elasticsearch", "spring data" ] }, { "id": 4, "title": "Elasticsearch Tutorial", "authors": [ { "name": "John Doe" } ], "tags": [ "elasticsearch" ] }, ]

Nous pouvons maintenant utiliser cette requête:

Page articleByTags = articleService.findByTagUsingDeclaredQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

3.2. Filtrer tous les documents

Un modèle de conception courant consiste à créer une vue de liste filtrée dans l'interface utilisateur qui affiche toutes les entités, mais permet également à l'utilisateur de filtrer en fonction de différents critères.

Disons que nous voulons renvoyer tous les articles filtrés par la balise sélectionnée par l'utilisateur:

@Query("{\"bool\": {\"must\": " + "{\"match_all\": {}}, \"filter\": {\"term\": {\"tags\": \"?0\" }}}}") Page findByFilteredTagQuery(String tag, Pageable pageable);

Encore une fois, nous utilisons Spring Data pour construire notre requête déclarée.

Par conséquent, la requête que nous utilisons est divisée en deux parties. La requête de notation est le premier terme, dans ce cas, match_all . La requête de filtre est la suivante et indique à Elasticsearch les résultats à ignorer.

Voici comment nous utilisons cette requête:

Page articleByTags = articleService.findByFilteredTagQuery("elasticsearch", PageRequest.of(0, 10)); // articleByTags will contain 3 articles [ 1, 3, 4] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(3)), hasProperty("id", is(4))) );

Il est important de réaliser que même si cela renvoie les mêmes résultats que notre exemple ci-dessus, cette requête fonctionnera mieux.

3.3. Filtrage des requêtes

Parfois, une recherche renvoie trop de résultats pour être utilisable. Dans ce cas, il est agréable d'exposer un mécanisme de filtrage qui peut réexécuter la même recherche, juste avec les résultats réduits.

Voici un exemple où nous restreignons les articles qu'un auteur a écrits, à ceux avec une balise spécifique:

@Query("{\"bool\": {\"must\": " + "{\"match\": {\"authors.name\": \"?0\"}}, " + "\"filter\": {\"term\": {\"tags\": \"?1\" }}}}") Page findByAuthorsNameAndFilteredTagQuery( String name, String tag, Pageable pageable);

Encore une fois, Spring Data fait tout le travail pour nous.

Voyons également comment construire cette requête nous-mêmes:

QueryBuilder builder = boolQuery().must( nestedQuery("authors", boolQuery().must(termQuery("authors.name", "doe")), ScoreMode.None)) .filter(termQuery("tags", "elasticsearch"));

On peut, bien entendu, utiliser cette même technique pour filtrer sur n'importe quel autre champ du document. Mais les tags se prêtent particulièrement bien à ce cas d'utilisation.

Voici comment utiliser la requête ci-dessus:

SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder) .build(); List articles = elasticsearchTemplate.queryForList(searchQuery, Article.class); // articles contains [ 1, 4 ] assertThat(articleByTags, containsInAnyOrder( hasProperty("id", is(1)), hasProperty("id", is(4))) );

4. Contexte du filtre

Lorsque nous créons une requête, nous devons faire la différence entre le contexte de requête et le contexte de filtre. Chaque requête dans Elasticsearch a un contexte de requête, nous devons donc être habitués à les voir.

Not every query type supports the Filter Context. Therefore if we want to filter on tags, we need to know which query types we can use.

The bool query has two ways to access the Filter Context. The first parameter, filter, is the one we use above. We can also use a must_not parameter to activate the context.

The next query type we can filter is constant_score. This is useful when uu want to replace the Query Context with the results of the Filter and assign each result the same score.

The final query type that we can filter based on tags is the filter aggregation. This allows us to create aggregation groups based on the results of our filter. In other words, we can group all articles by tag in our aggregation result.

5. Advanced Tagging

So far, we have only talked about tagging using the most basic implementation. The next logical step is to create tags that are themselves key-value pairs. This would allow us to get even fancier with our queries and filters.

For example, we could change our tag field into this:

@Field(type = Nested) private List tags;

Then we'd just change our filters to use nestedQuery types.

Une fois que nous comprenons comment utiliser des paires clé-valeur, il ne reste plus qu'à utiliser des objets complexes comme balise. Peu d'implémentations auront besoin d'un objet complet comme balise, mais il est bon de savoir que nous avons cette option si nous en avons besoin.

6. Conclusion

Dans cet article, nous avons couvert les bases de la mise en œuvre du balisage à l'aide d'Elasticsearch.

Comme toujours, des exemples peuvent être trouvés sur GitHub.

Suivant » Une implémentation de balisage simple avec persistance JPA 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