文章目录
1. 问题复现
1.1 索引库的结构
索引库的结构如下(其中 updateTime 字段为 date 类型)
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_smart"
},
"price": {
"type": "integer"
},
"image": {
"type": "keyword",
"index": false
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"sold": {
"type": "integer"
},
"commentCount": {
"type": "integer",
"index": false
},
"isAD": {
"type": "boolean"
},
"updateTime": {
"type": "date"
}
}
}
}
1.2 问题呈现
在 Java 代码中利用 BulkRequest 向 ElasticSearch 发送批量新增文档请求,发现没有一条数据添加成功,在控制台打印 bulk 方法返回的 BulkResponse 对象和遍历 BulkResponse 对象的 getItems() 方法返回的数组,都没有详细的报错信息
我百思不得其解,于是我单独拿出一条已经转换为 JSON 格式的数据,在 kibana 提供的 Dev Tools 控制台中往索引库新增一条文档
PUT /shopping_mall/_doc/317578
{
"brand": "RIMOWA",
"category": "拉杆箱",
"commentCount": 0,
"image": "https://m.360buyimg.com/mobilecms/s720x720_jfs/t6934/364/1195375010/84676/e9f2c55f/597ece38N0ddcbc77.jpg!q70.jpg.webp",
"isAD": false,
"name": "RIMOWA 21寸托运箱拉杆箱 SALSA AIR系列果绿色 820.70.36.4",
"price": 28900,
"sold": 0,
"stock": 10000,
"updateTime": "2023-05-06 11:06:17"
}
发送新增文档的请求后,在 Dev Tools 控制台看到了以下错误信息
{
“error” : {
“root_cause” : [
{
“type” : “mapper_parsing_exception”,
“reason” : “failed to parse field [updateTime] of type [date] in document with id ‘317578’. Preview of field’s value: ‘2023-05-06 11:06:17’”
}
],
“type” : “mapper_parsing_exception”,
“reason” : “failed to parse field [updateTime] of type [date] in document with id ‘317578’. Preview of field’s value: ‘2023-05-06 11:06:17’”,
“caused_by” : {
“type” : “illegal_argument_exception”,
“reason” : “failed to parse date field [2023-05-06 11:06:17] with format [strict_date_optional_time||epoch_millis]”,
“caused_by” : {
“type” : “date_time_parse_exception”,
“reason” : “Failed to parse with all enclosed parsers”
}
}
},
“status” : 400
}
1.3 问题剖析
Dev Tools 控制台给出的错误信息表明,Elasticsearch 在尝试将字符串 '2023-05-06 11:06:17'
解析为日期类型时,遇到了问题
ElasticSearch 默认支持两种日期格式:
strict_date_optional_time
epoch_millis
:自 1970 年 1 月 1 日 00:00:00 UTC 以来的毫秒数
以下是一些有效的 strict_date_optional_time
格式的示例:
2023-05-06
(仅日期)2023-05-06T11:06:17
(日期和时间,无时区)2023-05-06T11:06:17Z
(日期和时间,UTC 时区)2023-05-06T11:06:17+01:00
(日期和时间,带时区偏移量)
以下是一些无效的 strict_date_optional_time
格式的示例:
06-05-2023
(日期部分的顺序不正确)2023/05/06
(日期部分使用了错误的分隔符)2023-05-06 11:06:17
(时间部分没有使用T
分隔符)
ElasticSearch 在解析提供的日期字符串时失败了,因为提供的日期字符串 '2023-05-06 11:06:17'
并不匹配 ElasticSearch 默认支持的两种日期格式
2. 解决方案
2.1 在创建索引库时指定支持的日期格式(推荐)
我们在创建索引库时,可以指定支持的日期格式(通过 format 属性指定,format 属性是一个数组,可以接收多个日期格式)
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_smart"
},
"price": {
"type": "integer"
},
"image": {
"type": "keyword",
"index": false
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"sold": {
"type": "integer"
},
"commentCount": {
"type": "integer",
"index": false
},
"isAD": {
"type": "boolean"
},
"updateTime": {
"type": "date",
"format": ["yyyy-MM-dd HH:mm:ss"]
}
}
}
}
2.2 使用自定义的序列化器(使用 fastjson2,推荐)
实体类中 updateTime 字段使用的是 LocalDatetime 类型,数据库中 updateTime 字段是 datetime 类型(格式为 yyyy-MM-dd HH:mm:ss
),数据库中的 updateTime 字段转换为 LocalDatetime 类型时,会丢失纳秒级别的精度
丢失了纳秒级别的精度后,fastjson2 在将 LocalDatetime 类型的字段序列化为 JSON 时,日期格式为 yyyy-MM-dd HH:mm:ss
,我们可以自定义序列化规则
2.2.1 自定义序列化器
自定义一个类,实现 ObjectWriter 接口,重写 write 方法
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.writer.ObjectWriter;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class LocalDateTimeWriter implements ObjectWriter<LocalDateTime> {
// 私有构造函数,防止外部直接创建实例
private LocalDateTimeWriter() {
}
private static final LocalDateTimeWriter INSTANCE = new LocalDateTimeWriter();
// 提供一个公共的静态方法,用于获取单例
public static LocalDateTimeWriter getInstance() {
return INSTANCE;
}
@Override
public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type type, long features) {
// 检查对象是否为 LocalDateTime 类型
if (object instanceof LocalDateTime) {
// 使用指定的格式将 LocalDateTime 对象转换为字符串
jsonWriter.writeString(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss").format((LocalDateTime) object));
} else {
// 对于其他类型的对象,直接使用 JSON 序列化器进行处理
jsonWriter.writeAny(object);
}
}
}
2.2.2 注册序列化器
fastjson2 提供了 JSON.register(Type type, ObjectWriter<?> objectWriter)
方法,该方法可以注册指定类型的序列化器,在使用时默认全局生效
JSON.register(LocalDateTime.class, LocalDateTimeWriter.getInstance());
2.3 在实体类的日期字段上添加 @JSONField 注解(使用 fastjson2,不推荐)
不推荐使用该方式,因为该注解会同时影响序列化和反序列化时的日期格式
在实体类的日期字段上添加 @JSONField 注解后,会按指定的日期格式进行序列化和反序列化
@JSONField(format = "yyyy-MM-dd'T'HH:mm:ss")
ter.getInstance());
## 2.3 在实体类的日期字段上添加 @JSONField 注解(使用 fastjson2,不推荐)
> ***不推荐使用该方式,因为该注解会同时影响序列化和反序列化时的日期格式***
在实体类的日期字段上添加 @JSONField 注解后,***会按指定的日期格式进行序列化和反序列化***
```java
@JSONField(format = "yyyy-MM-dd'T'HH:mm:ss")