最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

java springboot整合elasticsearch时关于LocalDateTime处理的方式

来源:博客园

环境:

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小时。其实原因我也知道,所以这里不探究原因,只做处理。

我在这个过程中,曾进入一个误区,就是我就是要录入“2023-06-06 12:32:37.224”这个时间,然后我在ES中我还要显示“2023-06-06 12:32:37.224”这个时间,我不否认可能有这样的解决方案,但我想说的时间,没必要去纠结这个问题,因为在ES,是可以直接识别标准格式的时间,只要把时间的格式设置好了,在哪都是能识别的,时间显示永远不是问题,只要在kibana设置好了时区,前端自然会根据你选择的时区对时间进行转换显示。反而想让“2023-06-06 12:32:37.224”这样原始时间录入ES后仍然显示为“2023-06-06 12:32:37.224”会导致问题的复杂化。

在做处理之前,先简单了解下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@Inheritedpublic @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 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 **/@Slf4jpublic 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 ) {
LocalDateTime localDateTime = (LocalDateTime)value; 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;

关键词: