场景再现
spring-boot-data-elastic官方文档
版本
- elasticsearch 8.15.0
- 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创建的名字,而我的写法基本和官方一样,所以这个问题让我摸不到头脑
具体问题
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();
|
其他方案
还有其他方法,但是我都没测试过
手动传递 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));
}
|
这里我想吐槽一下官方文档,就是换个方法的事耗了半天,一直以为是注解使用不当的问题😅