Elasticsearchを使った簡単なタグ付けの実装

1概要

タグ付けは、データモデル内の項目を分類してフィルタ処理することを可能にする一般的なデザインパターンです。

この記事では、SpringとElasticsearchを使用してタグ付けを実装します。

Spring DataとElasticsearch APIの両方を使用します。

まず最初に、ElasticsearchとSpring Dataを入手するための基本をカバーするつもりはありません - /spring-data-elasticsearch-tutorial[ここ]のリンクを調べることができます。

2タグを追加する

  • タグ付けの最も簡単な実装は文字列の配列です** データモデルに新しいフィールドを追加することでこれを実装できます。

@Document(indexName = "blog", type = "article")
public class Article {

   //...

    @Field(type = Keyword)
    private String[]tags;

   //...
}

__Keyword field型の使用に注意してください。タグを正確に一致させるだけで結果を絞り込むことができます。これにより、 elasticsearchIsAwesome elasticsearchIsTerrible__のような類似しているが別々のタグを使用できます。

分析されたフィールドは部分的なヒットを返すでしょう、これはこの場合間違った動作です。

3ビルディングクエリ

タグを使用すると、クエリを興味深い方法で操作できます。他のフィールドと同じようにそれらを検索することも、 match all__クエリで結果をフィルタリングするためにそれらを使用することもできます。他のクエリと一緒に使用して結果を厳しくすることもできます。

3.1. タグを検索する

モデルに作成した新しい tag フィールドは、インデックス内の他のすべてのフィールドとまったく同じです。このような特定のタグを持つエンティティを検索できます。

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

この例ではSpring Data Repositoryを使用してクエリを構築していますが、https://docs.spring.io/spring/docs/3.0.x/javadoc-api/org/springframework/web/client/を使用することもできます。 Elasticsearchクラスターを手動で照会するためのRestTemplate.html[Rest Template]。

同様に、Elasticsearch APIを使用することができます。

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

索引に次の文書を使用しているとします。

----[    {
        "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"]    },]----

これで、このクエリを使うことができます。

Page<Article> 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. すべての文書をフィルタリングする

一般的なデザインパターンは、すべてのエンティティを表示するフィルターリストビューをUIに作成することですが、ユーザーはさまざまな基準に基づいてフィルターをかけることもできます。

ユーザーが選択したすべてのタグでフィルタされたすべての記事を返したいとしましょう。

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

繰り返しますが、宣言されたクエリを構築するためにSpring Dataを使用しています。

その結果、使用しているクエリは2つの部分に分割されます。スコアリングクエリは最初の用語、この場合は match all__です。フィルタクエリはnextで、どちらを破棄するかをElasticsearchに伝えます。

このクエリの使い方は次のとおりです。

Page<Article> 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)))
);

これは上の例と同じ結果を返しますが、このクエリはより良く機能することを理解することが重要です。

3.3. フィルタリングクエリ

検索によって返される結果が多すぎて使用できないことがあります。その場合は、結果を絞り込んで、同じ検索を再実行できるフィルタリングメカニズムを公開することをお勧めします。

これは、著者が書いた記事を、特定のタグを持つものだけに絞り込む例です。

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

繰り返しますが、Spring Dataはすべての作業を私たちのために行っています。

このクエリを自分で構築する方法も見てみましょう。

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

もちろん、これと同じテクニックを使って文書内の他のフィールドをフィルタリングすることもできます。しかし、タグはこのユースケースに特に適しています。

上記のクエリを使用する方法は次のとおりです。

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

4フィルタコンテキスト

クエリを作成するときには、クエリコンテキストとフィルタコンテキストを区別する必要があります。 Elasticsearchのすべてのクエリにはクエリコンテキストがあるため、それらを参照することに慣れておく必要があります。

すべてのクエリタイプがフィルタコンテキストをサポートしているわけではありません。したがって、タグをフィルタしたい場合は、どのクエリタイプを使用できるかを知る必要があります。

  • bool クエリには、フィルタコンテキストにアクセスする方法が2つあります 。最初のパラメータ filter は、上で使用したものです。コンテキストをアクティブにするために must not__ ** パラメータを使用することもできます。

  • 次にフィルタリングできるクエリタイプは constant score__ ** です。これは、クエリコンテキストをフィルタの結果に置き換えて、各結果に同じスコアを割り当てる場合に便利です。

  • タグに基づいてフィルタリングできる最後のクエリタイプは filter aggregation ** です。これにより、フィルタの結果に基づいて集計グループを作成できます。言い換えれば、私たちは私たちの集計結果でタグによってすべての記事をグループ化することができます。

5高度なタグ付け

これまでは、最も基本的な実装を使用したタグ付けについてのみ説明しました。次の論理的なステップは、それ自体がキーと値のペアであるタグを作成することです。これは私達が私達の質問とフィルターでさらに空想を得ることを可能にするでしょう。

たとえば、タグフィールドを次のように変更できます。

@Field(type = Nested)
private List<Tag> tags;

それからフィルタを nestedQuery 型を使うように変更します。

キーと値のペアの使用方法がわかれば、複雑なオブジェクトをタグとして使用するための小さなステップになります。タグとして完全なオブジェクトを必要とする実装はそれほど多くありませんが、必要に応じてこのオプションがあることを知っておくのは良いことです。

6. 結論

この記事では、Elasticsearchを使用してタグ付けを実装するための基本について説明しました。

いつものように、例はhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-elasticsearch[GitHubに追加]を見つけることができます。

"