查询方法

查询查找策略

Elasticsearch 模块支持所有基本查询构建功能,例如字符串查询、原生搜索查询、基于 Criteria 的查询,或者从方法名称派生查询。

声明式查询

从方法名称派生查询并非总是足够,并且/或者可能导致方法名称不可读。在这种情况下,可以使用 `@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 (当注解为 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 (当注解为 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 查询的方法名称。如果需要在仓库中实现此类功能,请在自定义仓库实现中使用带有 `CriteriaQuery` 的 `ElasticsearchOperations`。

方法返回类型

仓库方法可以定义为以下返回类型以返回多个元素

  • List<T>

  • Stream<T>

  • SearchHits<T>

  • List<SearchHit<T>>

  • Stream<SearchHit<T>>

  • SearchPage<T>

使用 @Query 注解

示例 2. 使用 `@Query` 注解在方法上声明查询。

传递给方法的参数可以插入到查询字符串中的占位符中。占位符的形式为 `?0`、`?1`、`?2` 等,分别代表第一个、第二个、第三个参数,依此类推。

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

设置为注解参数的字符串必须是有效的 Elasticsearch JSON 查询。它将作为查询元素的值发送到 Elasticsearch;例如,如果使用参数 John 调用该函数,它将生成以下查询体

{
  "query": {
    "match": {
      "name": {
        "query": "John"
      }
    }
  }
}
示例 3. 在接受 Collection 参数的方法上使用 `@Query` 注解

一个仓库方法,例如

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

将发起一个 IDs query 来返回所有匹配的文档。因此,使用包含 `["id1", "id2", "id3"]` 的 `List` 调用该方法将生成查询体

{
  "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` 参数,并且像普通 `String` 一样易于使用,例如下面的 `terms` 查询

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` 集合将生成以下 terms 查询

{
  "bool":{
    "must":[
      {
        "terms":{
          "name": ["name1", "name2"]
        }
      }
    ]
  }
}
示例 8. 访问 `Collection` 参数中的属性。

当 `Collection` 参数中的值不是纯 `String` 时,使用 SpEL 集合投影 会很方便

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 访问参数时,使用 Spring 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);
}