2022-04-18   


问题描述

系统一开始使用es没有使用那个@Document 实体来建立索引mapping,存储和更新也使用的map对象进行的。所以一开始没啥问题,后面某个业务需要固定mapping时,就尝试使用了那个Document实体。结果在查询时出现了java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor的错误。

排查

根据堆栈信息问题定位在at java.time.Instant.from(Instant.java:378) org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter.parse(ElasticsearchDateConverter.java:125),肯定和文档类的日期字段有关系了,文档存入没问题,那个Date字段是@Field(type = FieldType.Date, format = DateFormat.date) 格式是yyyy-MM-dd,打断点看一下为啥Instant.from要报错,结果不太清楚为啥会报错,断点执行评估表达式时我手动new 个完整Date格式化的字符串没问题,估计是Instant.from的参数有问题。

解决

最后发现是spring data elasticsearch处理日期写入和读出的代码不一致。
是个bug, 参考来源:https://segmentfault.com/q/1010000023765242

首先现在我们理清spring data elasticsearch处理过程大体为:

写入:Date -> Instant(TemporalAccessor) -> String
读出:String -> Parese(TemporalAccessor) -> Instant(TemporalAccessor) -> Date

不过再一次仔细看完写入的代码发现,其实Instant(TemporalAccessor)不是直接到String

@Override
public String format(TemporalAccessor accessor){
  return printer.format(DateFormatters.from(accessor));
}

Instant(TemporalAccessor)还经历了一次DateFormatters.from,
该方法是把一个TemporalAccessor转换为ZonedDateTime,再由ZonedDateTime转换成String,此时写入过程变为

写入:Date -> Instant(TemporalAccessor) -> ZonedDateTime(TemporalAccessor) -> String

这下也就释然了,为啥呢?因为java.util.Date在功能上就是和新的java8时间API的ZonedDateTime差不多的,这样的转换也是合理的。

反过来看读出时并没有这一步操作啊,其实这就不合理了,为啥呢?比如es里存的时间格式恰好没有时分秒,只有年月日,那它该怎么从一个年月日转换为Date呢?必须要补齐时分秒啊,这一步操作没有的话,是不能把TemporalAccessor转化为一个Instant的,Instant可是一个时间戳

所以正确的读出的过程应该是这样:

String -> Parese(TemporalAccessor) -> ZonedDateTime(TemporalAccessor) -> Instant(TemporalAccessor) -> Date

所以如果我们能把ElasticsearchDateConverter.parse的处理改为下面这样就可以了

public Date parse(String input){
	return new Date(Instant.from(DateFormatters.from(dateFormatter.parse(input))).toEpochMilli());
}

以上为网上找到的问题解析,原文中采用替换SimpleElasticsearchMappingContext Bean来暂时修复该问题,但是时过境迁,现在早就应该修复了。所以我采用更新依赖的方式解决,项目使用的elasticsearch为7.6,对应springboot为2.3.x, 直接使用最后2.3最后一个版本,修改spring-boot-dependencies版本为2.3.12.RELEASE,对应的spring-boot-starter-data-elasticsearch也会使用对应的最后的一个版本。

Q.E.D.


我并不是什么都知道,我只是知道我所知道的。