如何通过Java API Client与Spring Data Elasticsearch设文档属性为null
问题:Spring Data Elasticsearch Java API Client Upsert时保留Map的Null值
场景与需求
我有一个包含Todo类型Map的Schedule文档,结构如下:
{ "_index": "schedule-index", "_id": "sched-id", "_source": { "id": "sched-id", "todos": { "todo-id-1": { "id": "todo-id-1", "name": "laundry" }, "todo-id-2": { "id": "todo-id-2", "name": "dishes" } } } }
需要将其中一个Todo值设为null并保留对应的key,期望结果如下:
{ "_index": "schedule-index", "_id": "sched-id", "_source": { "id": "sched-id", "todos": { "todo-id-1": null, // <-- 设置为NULL "todo-id-2": { "id": "todo-id-2", "name": "dishes" } } } }
旧实现(RestHighLevelClient)正常工作
使用RestHighLevelClient时可实现该功能,代码如下:
// 使用HighLevelRestClient public BulkResponse upsert(List<Schedule> schedules) { // 'schedules' 包含1个id为sched-id的schedule,其中todo-id-1的值为null BulkRequest bulkRequest = new BulkRequest(); schedules.forEach(schedule-> { UpdateRequest updateRequest = new UpdateRequest("schedule-index", schedule.getId()) .docAsUpsert(true) .doc(asJson(schedule), XContentType.JSON); bulkRequest.add(updateRequest); }); BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT); return response; }
生成的请求如下:
{ "update": { "_index": "schedule-index", "_id": "sched-id" } } { "doc_as_upsert": true, "doc": { "id": "sched-id", "todos": { "todo-id-1": null // <----- 设置为NULL } } }
新实现(Java API Client)出现问题
切换为Spring Data Elasticsearch中的Java API Client后,生成的请求会忽略null值,map变为空:
// 使用Java API Client public BulkResponse upsert(List<Schedule> schedules) { // 'schedules' 包含1个id为sched-id的schedule,其中todo-id-1的值为null BulkRequest.Builder br = new BulkRequest.Builder(); schedules.forEach(schedule-> { br.operations(op -> op .update(ur -> ur .index("schedule-index") .id(schedule.getId()) .action(a -> a .docAsUpsert(true) .doc(schedule) ))); }); return client.bulk(br.build()); }
生成的请求如下:
{ "update": { "_index": "schedule-index", "_id": "sched-id" } } { "doc_as_upsert": true, "doc": { "id": "sched-id", "todos": { // <----- TODO MAP为空 } } }
补充说明:此问题类似根属性被忽略的情况,但这里是Map内部的null值被过滤。
解决方案
要让Java API Client在upsert时保留Map中的null值,核心是修改JSON序列化策略,因为默认策略会自动过滤null字段。以下两种方式可实现需求:
方式1:全局配置序列化规则
在配置Elasticsearch Java Client时,设置JsonMapper的序列化规则,强制包含null值:
@Bean public ElasticsearchClient elasticsearchClient(RestClient restClient) { JsonpMapper jsonpMapper = new JacksonJsonpMapper( Jackson2ObjectMapperBuilder.json() .serializationInclusion(JsonInclude.Include.ALWAYS) // 始终包含null字段 .build() ); return new ElasticsearchClient( new RestClientTransport(restClient, jsonpMapper) ); }
该方式全局生效,所有使用此客户端的序列化操作都会保留null值。
方式2:针对单个请求单独处理
如果不想全局修改,可在构建Bulk请求时手动序列化实体,生成包含null值的JSON:
public BulkResponse upsert(List<Schedule> schedules) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); // 临时设置包含null BulkRequest.Builder br = new BulkRequest.Builder(); schedules.forEach(schedule-> { // 手动序列化实体为包含null的JSON字符串 String scheduleJson = objectMapper.writeValueAsString(schedule); JsonData doc = JsonData.fromJson(scheduleJson); br.operations(op -> op .update(ur -> ur .index("schedule-index") .id(schedule.getId()) .action(a -> a .docAsUpsert(true) .doc(doc) // 使用手动序列化的JsonData ))); }); return client.bulk(br.build()); }
该方式仅对当前请求生效,不会影响其他业务的序列化行为。
原理说明
Java API Client默认使用的Jackson序列化策略是JsonInclude.Include.NON_NULL,会自动过滤null值字段。通过修改为Include.ALWAYS,可强制输出所有字段(包括值为null的Map条目),这样Elasticsearch就能接收到包含null值的Map,从而实现保留key并设置值为null的需求。
内容的提问来源于stack exchange,提问作者Seanimus
相关产品推荐
相关产品推荐

