最近壹哥的一个学生,在利用spring-data-elasticsearch访问ElasticSearch(ES)时,遇到了一个关于日期类型的BUG,困扰了很久。然后他就找壹哥给他解决,接下来壹哥就把解决的过程给大家复现一下,希望本文可以给遇到同样问题的同学一点启发。

一. 问题复现

1. 原始代码

我们先来看看他的POM.xml文件配置,如下所示:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.9.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
</dependencies>

实体类的代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "user_info",shards = 1,replicas = 1,createIndex = false)
public class UserInfo {

    @Id
    @Field(type = FieldType.Long)
    private Long id;

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String remark;

    @Field(type = FieldType.Date,format = DateFormat.date_time)
    private Date jobday;

}

创建索引的单元测试类代码如下:

@Test
void createIndex() {
    // 创建索引
    elasticsearchRestTemplate.createIndex(UserInfo.class);

    // 声明索引中 mapping部分
    elasticsearchRestTemplate.putMapping(UserInfo.class);
}

以下是ES中创建的索引 Mapping结构,其中日期类型的format为date_time。

关于ElasticSearch日期格式不一致的异常,可以这么解决-LMLPHP

看了以上代码,你能猜出来哪里有问题吗?其实这个bug是发生在写入操作中,代码如下:

@Test
void save() {

    final UserInfo userInfo = new UserInfo();
    userInfo.setId(100l);
    userInfo.setName("张三");
    userInfo.setJobday(new Date());
    userInfo.setRemark("张三是法外狂徒");

    userRepository.save(userInfo);
}

2. 异常信息

我们来看看产生的异常信息。

Caused by: ElasticsearchException[Elasticsearch exception [type=illegal_argument_exception, reason=Invalid format: "1669366585010" is malformed at "5010"]]
        at org.elasticsearch.ElasticsearchException.innerFromXContent(ElasticsearchException.java:509)
        at org.elasticsearch.ElasticsearchException.fromXContent(ElasticsearchException.java:420)
        at org.elasticsearch.ElasticsearchException.innerFromXContent(ElasticsearchException.java:450)
        at org.elasticsearch.ElasticsearchException.failureFromXContent(ElasticsearchException.java:616)
        at org.elasticsearch.rest.BytesRestResponse.errorFromXContent(BytesRestResponse.java:169)
        ... 87 more

通过查看报错信息,我们可以发现该bug是由于写入请求时,给定的日期格式与mapping中限定的date_time格式不一致导致的

二. 异常原因分析

既然是时间格式有问题,那么我们就需要知道写请求中的日期格式,与ES mapping中的date_time格式分别是什么。

从日志中我们可以看到,写入请求中的日期格式为 1669366585010,即long型整数的时间戳格式。根据ES的官方文档,我们可以看到date_time的格式要求如下:

关于ElasticSearch日期格式不一致的异常,可以这么解决-LMLPHP

 我们可以可以看到,ES里的格式默认为: yyyy-MM-dd'T'HH:mm:ss.SSSZ

这样bug产生的原因就很清晰了,该学生写入请求中的是 long,而ES索引中要求的是 yyyy-MM-dd'T'HH:mm:ss.SSSZ,这两个不一致,自然就报错了

三. 解决思路

既然我们现在明白了bug的产生原因,解决起来也就很容易了,只要把日期格式匹配上即可。现在springboot-data中写入的是long,只要让ES mapping中能够接收long格式即可。

1. 修改配置

修改后的代码配置如下所示:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "user_info",shards = 1,replicas = 1,createIndex = false)
public class UserInfo {

    @Id
    @Field(type = FieldType.Long)
    private Long id;

    @Field(type = FieldType.Keyword)
    private String name;

    @Field(type = FieldType.Text,analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
    private String remark;

    // 这里不要写 format即可,默认的date类型就可以接收 long值
    @Field(type = FieldType.Date)
    private Date jobday;

}

2. mapping结构

mapping结构如下所示,这里的date就可以接收long类型的值了。

关于ElasticSearch日期格式不一致的异常,可以这么解决-LMLPHP

 3. 单元测试

接下来我们重新进行单元测试,此时发现,代码可以正常执行通过了。

@Test
void save() {

    final UserInfo userInfo = new UserInfo();
    userInfo.setId(100l);
    userInfo.setName("张三");
    userInfo.setJobday(new Date());
    userInfo.setRemark("张三是法外狂徒");

    userRepository.save(userInfo);
}

关于ElasticSearch日期格式不一致的异常,可以这么解决-LMLPHP

 现在你知道这个问题怎么解决了吗?以后再遇到类似的问题,不要慌,沉下心来仔细想一下问题出在哪里,然后一点点针对该问题进行分析解决。如果你实在解决不了,给壹哥私信,我来帮你搞定。关注Java架构栈,干货天天都不断哦!

12-07 13:35