クエリメソッド

クエリルックアップ戦略

Elasticsearch モジュールは、文字列クエリ、ネイティブ検索クエリ、条件ベースのクエリ、メソッド名から派生したクエリなど、すべての基本的なクエリ構築機能をサポートします。

宣言されたクエリ

メソッド名からクエリを導出することは必ずしも十分ではなく、メソッド名が判読できない場合があります。この場合、@Query アノテーションを使用できます ( @Query アノテーションの使用を参照)。

クエリの作成

一般的に、Elasticsearch のクエリ作成メカニズムは、クエリメソッドの定義で説明されているとおりに機能します。Elasticsearch クエリメソッドがどのように変換されるかを示す簡単な例を次に示します。

例 1: メソッド名からのクエリ作成
interface BookRepository extends Repository<Book, String> {
  List<Book> findByNameAndPrice(String name, Integer price);
}

上記のメソッド名は、次の Elasticsearch json クエリに変換されます。

{
    "query": {
        "bool" : {
            "must" : [
                { "query_string" : { "query" : "?", "fields" : [ "name" ] } },
                { "query_string" : { "query" : "?", "fields" : [ "price" ] } }
            ]
        }
    }
}

Elasticsearch でサポートされているキーワードのリストを以下に示します。

表 1: メソッド名内でサポートされているキーワード
キーワード サンプル Elasticsearch クエリ文字列

And

findByNameAndPrice

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}

Or

findByNameOrPrice

{ "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }}

Is

findByName

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}

Not

findByNameNot

{ "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }}

Between

findByPriceBetween

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

LessThan

findByPriceLessThan

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }}

LessThanEqual

findByPriceLessThanEqual

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

GreaterThan

findByPriceGreaterThan

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }}

GreaterThanEqual

findByPriceGreaterThanEqual

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}

Before

findByPriceBefore

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }}

After

findByPriceAfter

{ "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }}

Like

findByNameLike

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

StartingWith

findByNameStartingWith

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

EndingWith

findByNameEndingWith

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

Contains/Containing

findByNameContaining

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }}

In (when annotated as FieldType.Keyword)

findByNameIn(Collection<String>names)

{ "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}

In

findByNameIn(Collection<String>names)

{ "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}}

NotIn (when annotated as FieldType.Keyword)

findByNameNotIn(Collection<String>names)

{ "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }}

NotIn

findByNameNotIn(Collection<String>names)

{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}

True

findByAvailableTrue

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }}

False

findByAvailableFalse

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }}

OrderBy

findByAvailableTrueOrderByNameDesc

{ "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] }

Exists

findByNameExists

{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}

IsNull

findByNameIsNull

{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}

IsNotNull

findByNameIsNotNull

{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}

IsEmpty

findByNameIsEmpty

{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}

IsNotEmpty

findByNameIsNotEmpty

{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}

GeoJson パラメーターを受け取る Geo-shape クエリを構築するためのメソッド名はサポートされていません。リポジトリにそのような機能が必要な場合は、カスタムリポジトリ実装で ElasticsearchOperations と CriteriaQuery を使用します。

メソッド戻り値の型

リポジトリメソッドは、複数の要素を返すために次の戻り値の型を持つように定義できます。

  • List<T>

  • Stream<T>

  • SearchHits<T>

  • List<SearchHit<T>>

  • Stream<SearchHit<T>>

  • SearchPage<T>

@Query アノテーションの使用

例 2: @Query アノテーションを使用してメソッドのクエリを宣言します。

メソッドに渡される引数は、クエリ文字列のプレースホルダーに挿入できます。プレースホルダーは、最初のパラメーター、2 番目のパラメーター、3 番目のパラメーターなど、?0?1?2 などの形式になります。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}")
    Page<Book> findByName(String name,Pageable pageable);
}

アノテーション引数として設定される文字列は、有効な Elasticsearch JSON クエリである必要があります。これはクエリ要素の値として Easticsearch に送信されます。たとえば、関数がパラメーター John で呼び出されると、次のクエリ本体が生成されます。

{
  "query": {
    "match": {
      "name": {
        "query": "John"
      }
    }
  }
}
例 3: Collection 引数を取るメソッドの @Query アノテーション

次のようなリポジトリメソッド

@Query("{\"ids\": {\"values\": ?0 }}")
List<SampleEntity> getByIds(Collection<String> ids);

ID クエリ (英語) を作成して、一致するすべてのドキュメントを返します。List または ["id1", "id2", "id3"] を指定してメソッドを呼び出すと、クエリ本体が生成されます。

{
  "query": {
    "ids": {
      "values": ["id1", "id2", "id3"]
    }
  }
}

SpEL 式の使用

例 4: SpEL 式で @Query アノテーションを使用してメソッドにクエリを宣言します。

@Query でクエリを定義する場合も、SpEL 式がサポートされます。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
        {
          "bool":{
            "must":[
              {
                "term":{
                  "name": "#{#name}"
                }
              }
            ]
          }
        }
        """)
    Page<Book> findByName(String name, Pageable pageable);
}

たとえば、関数がパラメーター John で呼び出されると、次のクエリ本体が生成されます。

{
  "bool":{
    "must":[
      {
        "term":{
          "name": "John"
        }
      }
    ]
  }
}
例 5: パラメータープロパティにアクセスします。

クエリパラメーター型として次のクラスがあるとします。

public record QueryParameter(String value) {
}

# シンボルでパラメーターにアクセスし、単純な . でプロパティ value を参照するのは簡単です。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
            {
              "bool":{
                "must":[
                  {
                    "term":{
                      "name": "#{#parameter.value}"
                    }
                  }
                ]
              }
            }
            """)
    Page<Book> findByName(QueryParameter parameter, Pageable pageable);
}

ここで、new QueryParameter("John") をパラメーターとして渡すと、上記と同じクエリ文字列が生成されます。

例 6: Bean プロパティにアクセスしています。

Bean プロパティへのアクセスもサポートされています。QueryParameter 型の queryParameter という名前の Bean がある場合、# ではなくシンボル @ を使用して Bean にアクセスでき、クエリメソッドで QueryParameter 型のパラメーターを宣言する必要はありません。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
            {
              "bool":{
                "must":[
                  {
                    "term":{
                      "name": "#{@queryParameter.value}"
                    }
                  }
                ]
              }
            }
            """)
    Page<Book> findByName(Pageable pageable);
}
例 7: SpEL および Collection パラメーター。

Collection パラメーターもサポートされており、次の terms クエリのように、通常の String と同じように簡単に使用できます。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
            {
              "bool":{
                "must":[
                  {
                    "terms":{
                      "name": #{#names}
                    }
                  }
                ]
              }
            }
            """)
    Page<Book> findByName(Collection<String> names, Pageable pageable);
}
elasticsearch json クエリを宣言するときに、コレクション値を引用符で囲まないでください。

List.of("name1", "name2") のような names のコレクションは、次の用語クエリを生成します。

{
  "bool":{
    "must":[
      {
        "terms":{
          "name": ["name1", "name2"]
        }
      }
    ]
  }
}
例 8: Collection パラメーターのアクセスプロパティ。

SpEL コレクション射影は、Collection パラメーターの値が単純な String ではない場合に使用すると便利です。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
            {
              "bool":{
                "must":[
                  {
                    "terms":{
                      "name": #{#parameters.![value]}
                    }
                  }
                ]
              }
            }
            """)
    Page<Book> findByName(Collection<QueryParameter> parameters, Pageable pageable);
}

これにより、QueryParameter コレクションからすべての value プロパティ値が新しい Collection として抽出され、上記と同じ効果が得られます。

例 9: @Param を使用してパラメーター名を変更する

SpEL でパラメーターにアクセスする場合、Sping Data の @Param アノテーションを使用してパラメーター名を別の名前に変更することも便利です。

interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("""
            {
              "bool":{
                "must":[
                  {
                    "terms":{
                      "name": #{#another.![value]}
                    }
                  }
                ]
              }
            }
            """)
    Page<Book> findByName(@Param("another") Collection<QueryParameter> parameters, Pageable pageable);
}