unknown index sort field:xx

在SpringBoot整合elasticsearch时使用@Setting,出现unknown index sort field:[address]

场景再现

spring-boot-data-elastic官方文档

版本

  1. elasticsearch 8.15.0
  2. springboot 3.0.2

POM依赖

1
2
3
4
5
<!--  elasticsearch客户端     -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置文件

1
2
3
4
5
spring:
  elasticsearch:
    password: XXl@20030711
    username: elastic
    uris: http://localhost:9200

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static final String INDEX_NAME = "mysql";

@Document(indexName = INDEX_NAME)
@Data
@Setting(
        sortFields = "address",
        sortModes = Setting.SortMode.max,
        sortOrders = Setting.SortOrder.asc,
        sortMissingValues = {  Setting.SortMissing._first }
)
private static class User{
    @Id
    private String id;
    @Field(type = FieldType.Text,analyzer = "ik_smart")
    @HighlightField(parameters = @HighlightParameters(preTags = "<b>",postTags = "</b>"))
    private String name;
    @Field(type = FieldType.Text,analyzer = "ik_smart")
    @HighlightField(parameters = @HighlightParameters(preTags = "<b>",postTags = "</b>"))
    private String nickName;
    @Field(type = FieldType.Text,index = false)
    private String pwd;
    @Field(type = FieldType.Keyword)
    private String address;
    @Field(type = FieldType.Date,index = false,format = DateFormat.date_hour_minute_second)
    private LocalDateTime createTime;
    @Field(type = FieldType.Date,index = false,format = DateFormat.date_hour_minute_second)
    private LocalDateTime updateTime;
    @Field(type = FieldType.Nested)
    private List<UserRole> roles;
}

@Data
@Accessors(chain = true)
private final static class UserRole{
    @Id
    private String id;
    @Field(type = FieldType.Text,analyzer = "ik_smart")
    private String roleEN;
    @Field(type = FieldType.Text)
    private String roleCN;
    @Field(type = FieldType.Integer,index = false)
    private Integer roleCode;
}

测试类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@SpringBootTest
public class Test {

    public static final String INDEX_NAME = "mysql";
    
    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;

    @Resource
    private ElasticsearchClient elasticsearchClient;
    
    @Test
    void test() throws IOException, InterruptedException {
        //删除索引    
        if (elasticsearchTemplate.indexOps(User.class).exists()) {
            elasticsearchClient.indices().delete(DeleteIndexRequest.of(builder -> builder.index(INDEX_NAME)));
        }
        //创建索引    
        elasticsearchTemplate.indexOps(User.class).create();
        //插入文档,MockUserBuilder.Builder是用于构建User对象无关这次的Bug
        MockUserBuilder.Builder builder = new MockUserBuilder.Builder();
        elasticsearchTemplate.save(List.of(builder.build(),builder.build(),builder.build()));
        //搜索指定索引下的所有文档,系统等待1秒因为查询为空,因为查询时可能正在插入  
        Thread.sleep(1000);
        SearchHits<User> search = elasticsearchTemplate.search(Query.findAll(),User.class);
        search.forEach(System.out::println);
    }
}   

执行如果如下:

1
2
3
4
5
6
7
Caused by: co.elastic.clients.elasticsearch._types.ElasticsearchException: [es/indices.create] failed: [illegal_argument_exception] unknown index sort field:[address]
	at co.elastic.clients.transport.rest_client.RestClientTransport.getHighLevelResponse(RestClientTransport.java:282)
	at co.elastic.clients.transport.rest_client.RestClientTransport.performRequest(RestClientTransport.java:148)
	at co.elastic.clients.elasticsearch.indices.ElasticsearchIndicesClient.create(ElasticsearchIndicesClient.java:266)
	at org.springframework.data.elasticsearch.client.elc.IndicesTemplate.lambda$doCreate$0(IndicesTemplate.java:138)
	at org.springframework.data.elasticsearch.client.elc.ChildTemplate.execute(ChildTemplate.java:71)
	... 73 more

也就是出现unknown index sort field:[address]问题,根据官方文档,sortFields使用的Java对象的属性名而不是为elasticsearch创建的名字,而我的写法基本和官方一样,所以这个问题让我摸不到头脑

img_1

具体问题

DEBUG源码在ElasticsearchIndicesClient类create方法发现问题

1
2
3
4
5
6
public CreateIndexResponse create(CreateIndexRequest request) throws IOException, ElasticsearchException {
    @SuppressWarnings("unchecked")
    JsonEndpoint<CreateIndexRequest, CreateIndexResponse, ErrorResponse> endpoint = (JsonEndpoint<CreateIndexRequest, CreateIndexResponse, ErrorResponse>) CreateIndexRequest._ENDPOINT;

    return this.transport.performRequest(request, endpoint, this.transportOptions);
}

在初次创建索引的时候发现request发送请求的内容没有mappings信息,没有mappings信息,但是setting信息又存在导致无法找到对应的字段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CreateIndexRequest: PUT /mysql 
{"settings":
    {
        "index":
        {"sort":
            {"field":["address"],
             "order":["asc"],"mode":["max"]},
        "number_of_shards":"1",
        "number_of_replicas":"1",
        "refresh_interval":"1s"}}
}

想在插入数据之前有mappings信息,就删除@Setting注解,但并没有解决问题,因为排序规则是跟mappings绑定的不能说一会删一会再加settings再更新Index

1
2
3
//创建索引
elasticsearchTemplate.indexOps(User.class).create();
elasticsearchTemplate.indexOps(User.class).putMapping();

解决方案

方案一

使用createWithMapping方法,亲测有效

1
2
//创建索引
elasticsearchTemplate.indexOps(User.class).createWithMapping();

其他方案

还有其他方法,但是我都没测试过

  1. 手动传递 mappings,手动调用 putMapping 方法来确保 mappings 在索引创建时被正确应用。

    1
    2
    3
    4
    5
    6
    7
    8
    
    IndexOperations indexOps = elasticsearchTemplate.indexOps(User.class);
    if (!indexOps.exists()) {
    // 创建索引并带上 settings
    indexOps.create(indexOps.createSettings(User.class));
    
        // 手动应用 mappings
        indexOps.putMapping(indexOps.createMapping(User.class));
    }
    

这里我想吐槽一下官方文档,就是换个方法的事耗了半天,一直以为是注解使用不当的问题😅