elasticsearch 整合springboot 的时候出现了日期格式转换的问题 ,找了好多也没有解决,请大佬帮助

实体类字段

@Field( type = FieldType.Date,
        format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss"
)
@JsonFormat (shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private Date createTcime;

测试类

@Test
public void TestSearch(){
    //构造查询条件
    NativeSearchQuery  searchQuery =new  NativeSearchQueryBuilder()
            //QueryBuilders 帮助类  multiMatchQuery 多条件匹配  第一个参数时要查询的文本 ,第二个参数时在那个字段里查询
            .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬","title","content"))
            //排序 按照type 这个字段排序,排序方式为倒叙
            .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
            .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
            .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
            //分页
            .withPageable(PageRequest.of(0,10))
            //结果高亮显示
            .withHighlightFields(
                    //高亮显示的字段      设置显示的标签
                    new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                    new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
            ).build();
    //查询
    Page<DiscussPost> search = discussPostRepository.search(searchQuery);
    System.out.println(search.getTotalElements()); //一共多少条数据
    System.out.println(search.getTotalPages()); //一共多少页
    System.out.println(search.getNumber());//当前在第几页
    System.out.println(search.getSize());//每页几条数据
    for (DiscussPost post : search) {
        //查看具体的数据
        System.out.println(post);
    }

}

错误信息

java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor: {YearOfEra=2019, MonthOfYear=4, DayOfMonth=25},ISO resolved to 10:14:05 of type java.time.format.Parsed

    at java.time.Instant.from(Instant.java:378)
    at org.springframework.data.elasticsearch.core.convert.ElasticsearchDateConverter.parse(ElasticsearchDateConverter.java:125)
    at org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentProperty$1.read(SimpleElasticsearchPersistentProperty.java:160)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.readValue(MappingElasticsearchConverter.java:281)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter$ElasticsearchPropertyValueProvider.getPropertyValue(MappingElasticsearchConverter.java:867)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.readProperties(MappingElasticsearchConverter.java:260)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.readEntity(MappingElasticsearchConverter.java:192)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.read(MappingElasticsearchConverter.java:172)
    at org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter.read(MappingElasticsearchConverter.java:81)
    at org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate$ReadDocumentCallback.doWith(AbstractElasticsearchTemplate.java:602)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate$ReadSearchDocumentResponseCallback.doWith(AbstractElasticsearchTemplate.java:626)
    at org.springframework.data.elasticsearch.core.AbstractElasticsearchTemplate$ReadSearchDocumentResponseCallback.doWith(AbstractElasticsearchTemplate.java:612)
    at org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate.search(ElasticsearchRestTemplate.java:272)
    at org.springframework.data.elasticsearch.repository.support.AbstractElasticsearchRepository.search(AbstractElasticsearchRepository.java:249)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.ImplementationInvocationMetadata.invoke(ImplementationInvocationMetadata.java:72)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:382)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:205)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:549)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:155)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:130)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy116.search(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy116.search(Unknown Source)
    at com.mango.community.dao.elasticsearch.DiscussPostRepositoryTest.TestSearch(DiscussPostRepositoryTest.java:98)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:686)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:212)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:208)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:137)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:71)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: InstantSeconds
    at java.time.format.Parsed.getLong(Parsed.java:203)
    at java.time.Instant.from(Instant.java:373)
    ... 113 more
阅读 14.4k
7 个回答

2020-11-04更新

这次直接整一个视频来,之前回答的稍微有点乱了,如果没看懂可以看看我录的解说视频叭(不过貌似声音有点小。。。)
https://www.bilibili.com/vide...

2020-10-16 更新

今天又要更新一下,昨天的回答最后不是提到我去spring data elasticsearch提了一个issues,还怕被那边大佬打脸说不是问题,经过昨晚大佬简短的确认问题,今天北京时间凌晨4点,大佬复现了问题,并且做了代码修改
image.png

也就是说,这确实是一个bug,而且修改方法也是按照我15号更新的方式去修改的,奈斯啊(这是spring data elasticsearch代码提交记录
image.png

也就是说String -> TemporalAccessor后一定要经历过一次再转ZonedDateTime之后,才能转成Instant,不过呢,spring data elasticsearch还没有发版本,所以想要同样的修改,还是可以按照15号我的方式做也行哦╰(_°▽°_)╯

2020-10-15 更新

昨天的回答,确实很牵强,毕竟不是按照题主的需求来做的修改,我今天再看了一下,终于自己确认了一些问题同时也暂时找到一个可以实现题主要求的方式

确认的问题:

spring data elasticsearch在处理实体属性类型为java.util.Dateread操作上有问题,也就是应该有bug,其实也就是在ElasticsearchDateConverter.parse的方法处理上
image.png

这个我在昨天初次回答的时候已经提到,有问题的地方就在于dateFormatter.parse(input)出来的结果TemporalAccessorInstant.from调用失败,但当时我没有仔细想,而是想办法去绕过,所以才有了昨天的回答

实现题主需求的方法:

我们忽略了一个问题就是,时间转换上,spring data elasticsearch其实都用的DateTimeFormatter,也就是无论是String -> Date 还是Date -> String都是采用DateTimeFormatter的方法,那既然能够写入,读出应该也是可以的,毕竟DateTimeFormatter可是jdk自带的时间格式化工具,不可能说它只实现了一半功能,所以说,现在读出不行,写入可以,说明spring data elasticsearch在使用DateTimeFormatter时,写入时传入的参数正确,读出时传入的参数错误,所以我们也就可以去比对一下它们两个过程到底怎么做的,有什么区别。首先现在我们理清spring data elasticsearch处理过程大体为:

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

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

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

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

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

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

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

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

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

为了达到这个效果,我们捋捋,同时去寻找spring data elasticsearch的扩展能力。

  1. 首先是我们的目标位置ElasticsearchDateConverter没有托管给Spring,它属于SimpleElasticsearchPersistentProperty的一个处理过程new出来的
  2. SimpleElasticsearchPersistentProperty也没有托管给Spring,它属于SimpleElasticsearchMappingContext,终于这个Context是被托管的,且有可扩展点

ElasticsearchDataConfiguration中我们可以看到扩展点配置
image.png

所以我们只需要去

  1. 补一个SimpleElasticsearchMappingContextBean覆盖默认的,简单取名为CustomSimpleElasticsearchMappingContext
  2. CustomSimpleElasticsearchMappingContext中要使用新的CustomSimpleElasticsearchPersistentProperty,而不是之前的SimpleElasticsearchPersistentProperty
  3. 然后CustomSimpleElasticsearchPersistentProperty要使用的是CustomElasticsearchDateConverter而不是ElasticsearchDateConverter
  4. 最后CustomElasticsearchDateConverter中修改parse即可

不知道这里几个类如果没看源码可能会绕晕点,不过还不算复杂,只是线性的依赖,我简单梳理一个图描述一下修改的想法

image.png

由于三个类里面的东西比较多,我干脆甩到了github上,可以自行查阅

同时,由于我认为这也是一个bug,所以直接跑去spring data elasticsearch官网提了一个issues,我简单截一个图,其实就是用蹩脚的英语描述一下。。。

image.png

hhhhh,虽然不知道会不会被打脸。。。。后续他们有回答的话,我会再来更新。。。就酱(๑•̀ㅂ•́)و✧

2020-10-14回答

试了哈,确实有这样的问题,先说哈我最后牵强的解决方案...
你的createTime字段修改一下,改为

@Field( type = FieldType.Date, format = DateFormat.date_hour_minute_second)
@JsonFormat (shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
private LocalDateTime createTime;

也就是

  1. format改为date_hour_minute_second,它是标准ISO 8601的时间表示方式,存入数据库的格式为yyyy-MM-dd'T'HH:mm:ss,也就是比你之前的多了个T
  2. 数据类型由Date改为LocalDatetime

自己demo了一下是可以的,你可以试试看

至于为啥之前你这样写报错,原因说起来稍微复杂一点,我也是看了才知道

首先从报错入手,Instant.from报错,这是还没有转成最终的Date就失败了
image.png

那继续看出错具体原因为,在当前类的getLong方法中,也就是当前类不支持Instant传给它的Field
image.png

而当前类就是Instant.from的入参TemporalAccessor,此时实现为Parsed,虽然Parsed它也是一个TemporalAccessor,跟我们平常了解到的LocalDateLocalDateTime稍有不一样,但是都是TemporalAccessor,也就是都是时间的一个访问方式,只是它侧重于时间数据被parse后的存储,我个人理解为就是一种中间过程类,并不适合我们用,是DateTimeFormatter解析日期字符串后的产物,然后我们再可以根据TemporalAccessor的特性,query出我们要的时间形式即可

也就是说只要用DateTimeFormatter解析再加上用Instant做转换,永远都会报错,那我们接着看源码,可以不可以转变这样的结果

那就是继续沿着堆栈继续往上看,来到SimpleElasticsearchPersistentProperty$1.read
image.png

这一行代码豁然开朗,因为这里有个重要的分支isTemporalAccessor
image.png

TemporalAccessor不用多说,是咱们的时间访问入口,所以这就可以很自然的接入到Java8的时间API里了

直接查看它的实现方式,很明显,用了TemporalAccessorfrom方法,而这个是根据type来的,不用多说,这个type肯定就是实体类里定义的类型了
image.png

既然如此,我们就可以根据Java8的时间类型+题主业务需要来选择具体的TemporalAccessor,那我首先想到的用LocalDateTime

一试结果还是报错
image.png

此时从这里看temporalParsed,所以根本原因就是Parsedquery方法返回的LocalDate为空,而进入Parsed.query
image.png

其实就是因为Parsed里的date属性为空,那按照之前我们处理Instant的方案来说,这也就改不了么?

不是的,因为此时情况不一样,我们回顾一下此时的情况
LocalDateTimefrom方法调用了一个DateTimeFormatter解析后的结果Parsed失败

???

为啥我会有问号,因为日常我用java8时间api解析时间,不就是这么做的么?

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
TemporalAccessor parse = dateTimeFormatter.parse("2020-10-14 11:12:39");
LocalDateTime localDateTime = LocalDateTime.from(parse);

写了一万遍了,突然说这个不行?显然不是啊,所以比对一下这个简单的处理过程,减去相同变量,根本原因那就是这个DateTimeFormatter

扫了一下源码,在DateFormatters.forPattern中藏着所有秘密,这里就是具体的pattern对应的DateFormatter,一大堆if else,太多了,简单截个小图吧
image.png

那咱们现在写的patternyyyy-MM-dd HH:mm:ss,根据这些if else,恰好是最后一个else

image.png

注意这里的DateTimeFormatterBuilder,根据后面的代码可知,这里的DateTimeFormatterBuilder创建出来的DateTimeFormatter为最终解析咱们createTimeDateTimeFormatter

比对下我之前没有问题的写法,走入DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
其实为:

new DateTimeFormatterBuilder()
            .appendPattern(pattern)
            .toFormatter()

spring data Elasticsearch里的为:

new DateTimeFormatterBuilder()
            .appendPattern(input)
            .toFormatter(Locale.ROOT)
            .withResolverStyle(ResolverStyle.STRICT))

也就是说它比我的多了一个Locale.ROOTResolverStyle.STRICT,通过简单的控制变量法把我之前的例子分别加上其中一个后,发现导致报错的是ResolverStyle.STRICT

这个ResolverStyle代表解析日期和时间的程度,默认的是STRICT也就是严格,而我之前的有没有选那就是SMART智能

但从源码看起来,是没有机会修改这个ResolverStyle,那我当时想到只能是改变一下我们自己了,也就是按照ISO 8601的标准日期和时间展示格式来存储,因为yyyy-MM-dd HH:mm:ss并不是标准的
image.png

那既然是标准的,那DateFormat肯定就不会是custom,根据elasticsearch官网的说明
image.png

我最终采用了date_hour_minute_second,之后再试了一下,就ok了

不过由于其实不算真正解决题主的问题,毕竟需求来说,最终数据库存储还是需要yyyy-MM-dd HH:mm:ss的,我暂时也没有想到更好的方法,只是根据现有的线索算是临时方案吧,如果有更好的,可以@我一下我,那拜了个拜︿( ̄︶ ̄)︿


(题外的话:其实为啥我很有兴趣看下去,是我之前也整理过java8时间api的一些东西,尤其是接口设计方面,我尝试用自己的理解去解析时间api,同时用自己的语言写出了《Java8 时间API及主要接口个人理解》,可以直接看第三部分Java8时间接口),当时只是对一些顶层接口有了点印象,不过今天其实还算是有很多收获,尤其是关于era(世纪),不过era我在刚的回答里没有提到,我的文章里其实也没有提到,但是这次最终为啥date_hour_minute_second的方式又可以了,其实跟era是或多或少有些关系的,因为最终date_hour_minute_second里时间的fieldChronoField.YEAR,而之前错误的为ChronoField.YEAR_OF_ERA

新手上路,请多包涵

这个bug是哪一个版本的呢?我也是遇到这个问题,用Date,能写入,不能转出

我们之前项目也遇到过,我们用的简单的方式,把时间转换成时间戳,存long型

 遇到一个类似的问题,代码中设置如下类型

@Field( type = FieldType.Date)
private Date createTcime;

这时 esmapping 字段类型为 date ,es实际存储的是 createTcime 的时间戳(long),今天发现当日期在 1970-01-25 15:20:00 之前(时间戳刚好对应 int 最大值2100000000),可以成功储存到es,并保存成时间戳,但是从es 查询数据回来映射java类时,会报 int 无法被转换为date. 而日期在 1970-01-25 15:20:00之后,读写es都是可以成功的。
猜想在进行对es返回数据转换java Date类 解析时 ,会判断是否满足long类型,满足就当时间戳解析,否则当int类型处理,所以报错了

解决方案

给es 的entityMapper添加一个int转date类型的convert,其中ElasticsearchConfig extends AbstractElasticsearchConfiguration

image.png
image.png

新手上路,请多包涵

Java Time 使用 uuuu 解析年,yyyy 是指 year of era,严格模式下解析的时候使用 yyyy 会报错,因为缺少 era。例如:

val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
    .withResolverStyle(ResolverStyle.STRICT)
val date = LocalDate.parse("20200101", formatter)

解析的时候会报错:

Text '20200101' could not be parsed: Unable to obtain LocalDate from TemporalAccessor: {MonthOfYear=1, YearOfEra=2020, DayOfMonth=1},ISO of type java.time.format.Parsed

使用 year of era 完整的日期字符应该为 公元 20200101,对应的匹配表达式为 G yyyyMMddG 表示 era,解析的时候和 Locale 有关,Locale.CHINA 的是 公元Locale.USAD,上面的代码修改后为:

val formatter = DateTimeFormatter.ofPattern("G yyyyMMdd")
    .withResolverStyle(ResolverStyle.STRICT)
    .withLocale(Locale.CHINA)
val date = LocalDate.parse("公元 20200101", formatter)

解决方法是使用 uuuu 替换 yyyy,或者不使用 STRICT 模式,或者指定默认字段,参考:https://stackoverflow.com/a/3...

新手上路,请多包涵

有一个比较简单的解决方案,像这样加上注解即可,甚至可以自己搞个复合注解,每次填上自己的复合注解就行了

image.png

这种解决办法实现起来挺简单,EsLocalDateTimePropertyValueConverter的实现代码如下:

public enum EsLocalDateTimePropertyValueConverter implements PropertyValueConverter {

    INSTANCE;

    @NotNull
    @Override
    public Object read(Object value) {
        //这里自己处理时间,要不要+8自己决定
        return LocalDateTime.parse(value.toString(), DateTimeFormatter.ISO_DATE_TIME);
    }

    @NotNull
    @Override
    public Object write(Object value) {
        if (!TemporalAccessor.class.isAssignableFrom(value.getClass())) {
            return value.toString();
        }
        //es里默认只存储不包含时区的时间,所以这里需要转换成UTC时间,避免在kibana里看到的时间比实际时间多8小时
        ZonedDateTime zonedDateTime = ((LocalDateTime) value).atZone(ZoneId.systemDefault()).withZoneSameInstant(ZoneOffset.UTC);
        return DateTimeFormatter.ISO_DATE_TIME.format(zonedDateTime);
    }
}

下面是实现原理:
碰到这个情况在debug的时候不难找到org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentProperty#initPropertyValueConverter这个方法
看一下这个方法的第一行
image.png
image.png
这里就很明显了,他在尝试找有没有ValueConverter这个注解,有的话就把ValueConverter配置的的PropertyValueConverter设置为当前的propertyValueConverter,这样就可以实现自己定义序列化方式了

推荐问题
宣传栏