查询方法
查询查找策略
Elasticsearch 模块支持所有基本的查询构建功能,如字符串查询、原生搜索查询、基于条件的查询,或者从方法名派生查询。
声明式查询
从方法名派生查询并不总是足够,并且/或者可能导致方法名不可读。在这种情况下,可以使用 @Query 注解(请参阅使用 @Query 注解)。
另一种方式是使用搜索模板(请参阅使用 @SearchTemplateQuery 注解)。
查询创建
一般来说,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 参数的地理形状查询的方法名。如果您需要在仓库中拥有这样的功能,请在自定义仓库实现中结合使用 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 查询。它将作为 query 元素的值发送给 Elasticsearch;例如,如果使用参数 John 调用该函数,它将生成以下查询体:
{
"query": {
"match": {
"name": {
"query": "John"
}
}
}
}
示例 3. 在接收 Collection 参数的方法上使用 @Query 注解
如下仓库方法
@Query("{\"ids\": {\"values\": ?0 }}")
List<SampleEntity> getByIds(Collection<String> ids);
将发起一个 IDs 查询,以返回所有匹配的文档。因此,使用 ["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);
}
使用 @SearchTemplateQuery 注解
在使用 Elasticsearch 搜索模板时(请参阅搜索模板支持),可以通过为仓库方法添加 @SearchTemplateQuery 注解来指定该方法应使用某个模板。
假设存储了一个名为 "book-by-title" 的搜索模板,并且该模板需要一个名为 "title" 的参数,那么可以像这样定义一个使用该搜索模板的仓库方法:
interface BookRepository extends ElasticsearchRepository<Book, String> {
@SearchTemplateQuery(id = "book-by-title")
SearchHits<Book> findByTitle(String title);
}
仓库方法的参数以键/值对的形式发送给搜索模板,其中键是参数名,值取自方法被调用时的实际值。