环境:
springboot version:2.7.2
spring-data-elasticsearch: 2.7.2
elaseicsearch: 7.10.1
以上是我测试环境,如果环境相差太大,可能会有所差异,仅做参考。
写博客主要是为了记录今天对LocalDateTime处理的过程。
首先,简单介绍一下业务环境。我们的业务库有大量数据,需要导到Elasticsearch中,以使用Elasticsearch的全文索。但有个问题,就是在业务库的时间,是2023-06-06 12:32:37.224这种模式的,并且,业务库的实体时间属性使用的都是LocalDateTime,如果直接录入到ES中,会发现,时间多了8小时。其实原因我也知道,所以这里不探究原因,只做处理。
在做处理之前,先简单了解下LocalDateTime,如下图,可以看到,LocalDateTime相当简单,关于LocalDateTime的知识,大家可以参考LocalDateTime - 廖雪峰的官方网站 (liaoxuefeng.com),因为LocalDateTime无时区的概念,如果仅是“2023-06-06 12:32:37.224”会被默认为0时区的时间录进去,而中国是东八区,比0时区早8小时。这样,自然就产生了时间差,多了了8小时。
那??直接把时间减去8小时,再录进去,不就行了吗?这样也对,比较暴力。当然,处理方法不只一种,我只说我的方式。
再次,我们来看ES里的时间,最后录入的时间,和原来的时间相比,多了个T和Z。
{ "fields": { "@timestamp": [ "2023-06-06T04:32:37.224Z" ], "ywblStartTime": [ "2023-06-06T04:32:37.224Z" ], "ywblEndTime": [ "2023-06-06T04:32:37.224Z" ] } }
要想处理时间,得先了解这个T和Z是什么意思。T可以忽略,就是个分隔符,看成空格即可。关键在于Z这个,Z表示的是UTC,即国际标准时间,可以理解为0时区的时间。回到上面,如果我们在Mysql中的数据库的时间是2023-06-06 12:32:37.224,那对应的国际时间就是2023-06-06T04:32:37.224Z。明显发现,标准时间表示中,是少8小时的。这也是我这次的目标时间。
因此,要做的处理就有两个,一个是时间录入到ES时,需要从LocalDateTime转换成国际标准时间,查询ES时,需要把ES的时间转回LocalDateTime。
通过查看包org.springframework.data.elasticsearch.annotations,看到一个切面类ValueConverter,需要一个PropertyValueConverter参数,如下,没跑了,就是你了!
@Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.ANNOTATION_TYPE }) @Documented @Inherited public @interface ValueConverter { /** * Defines the class implementing the {@link PropertyValueConverter} interface. If this is a normal class, it must * provide a default constructor with no arguments. If this is an enum and thus implementing a singleton by enum it * must only have one enum value. * * @return the class to use for conversion */ Class<? extends PropertyValueConverter> value(); }
下面是接口类PropertyValueConverter,其实也相当简单,只要实现这两个方法即可。英文可以看看,其中write是从实体到ES的转换,这里要返回的是String ,read则是从ES于实体的转换,返回的是LocalDateTime
public interface PropertyValueConverter { /** * Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a * String representation. * * @param value the value to convert, must not be {@literal null} * @return The elasticsearch property value, must not be {@literal null} */ Object write(Object value); /** * Converts an elasticsearch property value to a property value. * * @param value the elasticsearch property value to convert, must not be {@literal null} * @return The converted value, must not be {@literal null} */ Object read(Object value); }
下面直接上代码吧:
/** * 关于类的说明 * * @Author lythen * @date 2023/6/6 11:34 **/ @Slf4j public class LocaDateTimeEsConverter implements PropertyValueConverter { /** * Converts a property value to an elasticsearch value. If the converter cannot convert the value, it must return a * String representation. * * @param value the value to convert, must not be {@literal null} * @return The elasticsearch property value, must not be {@literal null} */ @Override public Object write(Object value) { if(value instanceof LocalDateTime ) {
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime,ZoneId.of("GMT+8"));
return zonedDateTime.toInstant().toString();
} return value; } /** * Converts an elasticsearch property value to a property value. * * @param value the elasticsearch property value to convert, must not be {@literal null} * @return The converted value, must not be {@literal null} */ @Override public Object read(Object value) { try { String strDateTime = value.toString(); Instant instant = null; if(value.getClass().getName().contains("Long")){ long longTime = (Long) value; Date date = new Date(longTime); instant =date.toInstant(); } else{ instant = Instant.parse(strDateTime); } LocalDateTime localDateTime = LocalDateTime.ofInstant(instant,ZoneId.of("GMT+8")); return localDateTime; }catch (Exception e){ log.error(e.getMessage(),e); return null; } } }
转换的思路如下:
实体->ES,使用带区的时间类ZonedDateTime将原始时间识别为东八区时间(这里是识别,而非转换),以下为调试时的属性。这里的时间要和之前带Z的时间区分一下,这里能看到时间是“2023-06-06T14:56:50.880339300+08:00[GMT+08:00]”,这里表示这个是东八区的时间,而不是国际标准时间。最后zonedDateTime.toInstant(),这里其实是个取巧的方式,因为Instant自身也无时区概念,参考Instant - 廖雪峰的官方网站 (liaoxuefeng.com),它指的是时间戳,或者就理解为0时间区的时间也没什么问题,那使用zonedDateTime.toInstant()就是把东八区的时间转成了0时间区时间,再用个toString(),就转换成了UTC的国际标准时间,也就是少了8小时带Z的那个时间格式。
ES->实体
这个就没什么好说了,其实比较好理解,主要就是遇到了存储为Long的时间,因此多做了一个判断,后面也就是从UTC国际标准时间,转换为LocalDateTime的过程,转换之后,加了8小时,那我们查询的结果就正确了。
最后,在实体加上注解:
@Field(type = FieldType.Date, format = {DateFormat.date_hour_minute_second_millis}) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonSerialize(using = LocalDateTimeSerializer.class) @ValueConverter(LocaDateTimeEsConverter.class) LocalDateTime ywblStartTime; @Field(type = FieldType.Date, format = {DateFormat.date_hour_minute_second_millis}) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonSerialize(using = LocalDateTimeSerializer.class) @ValueConverter(LocaDateTimeEsConverter.class) LocalDateTime ywblEndTime;
- elasticsearch LocalDateTime springboot 方式 javaelasticsearch localdatetime springboot方式 方式elasticsearch java localdatetime springboot yyyy-mm-dd格式 localdatetime element-ui springboot easyexcel localdatetime时间java java localdatetime yyyy-mm-dd格式 class java localdatetime cannot localdatetime日期 类型java localdatetime 20230630 java time localdatetime java8 java date