无法转换 Spring 数据 Elasticsearch 中 属性 'createdTime' 的值“2022-01-30T20:44:43.786438”
Unable to convert value '2022-01-30T20:44:43.786438' of property 'createdTime' in Spring Data Elasticsearch
我最近更新到 spring data elasticsearch
的最新版本。当我试图保存或读取数据时,我总是收到以下错误,
Unable to convert value '2022-01-30T20:44:43.786438' of property
'createdTime'
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds
以前在旧版本中我使用的是以下内容,
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
LocalDateTime createdTime;
所以我现在尝试了以下方法。但是在将数据保存到 elasticsearch 时再次抛出上述相同的错误。这样读取数据就可以了
@Field(type = FieldType.Date, format = {}, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSSX")
LocalDateTime createdTime;
我该如何解决这个问题?为什么读取数据时可以正常工作,但保存时却不行?而且我还有以这种日期时间格式保存的旧数据。所以我需要一个解决这个问题的方法来克服这个错误,并使它与我拥有的旧数据兼容。提前致谢。
LocalDateTime
没有时区。但是您想使用具有时区 (X
) 的模式来转换它。因此,您会收到时间对象不包含偏移量(时区)信息的错误。
您应该将定义更改为:
@Field(type = FieldType.Date, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSS", format = {})
或者如果您需要时区,请使用 ZoneDateTime
。
这在以前的版本中起作用的原因是我们可以使用 Elasticsearch 核心库中的一些 class,这些库做了很多自定义处理和回退。我们现在不能再使用它们了,所以我们使用 JDK 中的标准 DateTimeFormatter
和已定义的模式,但有时不太灵活。
编辑 2022 年 1 月 31 日:
正如评论中所述,不仅从 Elasticsearch 返回的字符串不包含区域信息,还有一些情况包含区域信息。
因此,当您需要削减没有区域信息的值时,您需要一个 LocalDateTime
,而当有区域信息可用时,您需要一个 ZonedDateTime
。仅使用 @Field
注释参数没有开箱即用的解决方案,但正如您所写,您使用的是最新版本的 Spring Data Elasticsearch - 应该是 4.3。从这个版本开始,您可以为单个属性定义自定义转换器(不仅是通常一次为整个 classes)。
让我来展示如何做到这一点:
我把createdTime
属性的class改成了ZonedDateTime
,当读入没有zone info的数据时,我会设置成UTC .也可以保留 LocalDateTime
并从最终读取值中删除时区。
重要的是附加注释:
@Field(type = FieldType.Date, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSSX||uuuu-MM-dd'T'HH:mm:ss.SSS", format = {})
@ValueConverter(CustomZonedDateTimeConverter.class)
private ZonedDateTime someDate;
保留 @Field
注释的原因是在应用程序创建索引时写入正确的映射。
@ValueConverter
注释导致创建给定 class 的转换器(必须具有 no-arg 构造函数)并附加到此 属性。它不会用于应用程序中的任何其他 ZonedDateTime
属性,这使得它不同于标准 Spring 数据转换器。实现如下(Java 17语法中的instanceof
):
class CustomZonedDateTimeConverter implements PropertyValueConverter {
private final DateTimeFormatter formatterWithZone = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
private final DateTimeFormatter formatterWithoutZone = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSS");
@Override
public Object write(Object value) {
if (value instanceof ZonedDateTime zonedDateTime) {
return formatterWithZone.format(zonedDateTime);
} else {
return value;
}
}
@Override
public Object read(Object value) {
if (value instanceof String s) {
try {
return formatterWithZone.parse(s, ZonedDateTime::from);
} catch (Exception e) {
return formatterWithoutZone.parse(s, LocalDateTime::from).atZone(ZoneId.of("UTC"));
}
} else {
return value;
}
}
}
写的部分很简单,写划日期时间就可以了。在阅读时,我首先尝试解析分区日期时间。如果失败,我会尝试在没有任何区域的情况下进行解析。成功后,我将区域设置为 UTC 和 return 值。
如果需要,您可以将其扩展到更多不同的格式。
我最近更新到 spring data elasticsearch
的最新版本。当我试图保存或读取数据时,我总是收到以下错误,
Unable to convert value '2022-01-30T20:44:43.786438' of property 'createdTime' Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds
以前在旧版本中我使用的是以下内容,
@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
LocalDateTime createdTime;
所以我现在尝试了以下方法。但是在将数据保存到 elasticsearch 时再次抛出上述相同的错误。这样读取数据就可以了
@Field(type = FieldType.Date, format = {}, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSSX")
LocalDateTime createdTime;
我该如何解决这个问题?为什么读取数据时可以正常工作,但保存时却不行?而且我还有以这种日期时间格式保存的旧数据。所以我需要一个解决这个问题的方法来克服这个错误,并使它与我拥有的旧数据兼容。提前致谢。
LocalDateTime
没有时区。但是您想使用具有时区 (X
) 的模式来转换它。因此,您会收到时间对象不包含偏移量(时区)信息的错误。
您应该将定义更改为:
@Field(type = FieldType.Date, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSS", format = {})
或者如果您需要时区,请使用 ZoneDateTime
。
这在以前的版本中起作用的原因是我们可以使用 Elasticsearch 核心库中的一些 class,这些库做了很多自定义处理和回退。我们现在不能再使用它们了,所以我们使用 JDK 中的标准 DateTimeFormatter
和已定义的模式,但有时不太灵活。
编辑 2022 年 1 月 31 日:
正如评论中所述,不仅从 Elasticsearch 返回的字符串不包含区域信息,还有一些情况包含区域信息。
因此,当您需要削减没有区域信息的值时,您需要一个 LocalDateTime
,而当有区域信息可用时,您需要一个 ZonedDateTime
。仅使用 @Field
注释参数没有开箱即用的解决方案,但正如您所写,您使用的是最新版本的 Spring Data Elasticsearch - 应该是 4.3。从这个版本开始,您可以为单个属性定义自定义转换器(不仅是通常一次为整个 classes)。
让我来展示如何做到这一点:
我把createdTime
属性的class改成了ZonedDateTime
,当读入没有zone info的数据时,我会设置成UTC .也可以保留 LocalDateTime
并从最终读取值中删除时区。
重要的是附加注释:
@Field(type = FieldType.Date, pattern = "uuuu-MM-dd'T'HH:mm:ss.SSSX||uuuu-MM-dd'T'HH:mm:ss.SSS", format = {})
@ValueConverter(CustomZonedDateTimeConverter.class)
private ZonedDateTime someDate;
保留 @Field
注释的原因是在应用程序创建索引时写入正确的映射。
@ValueConverter
注释导致创建给定 class 的转换器(必须具有 no-arg 构造函数)并附加到此 属性。它不会用于应用程序中的任何其他 ZonedDateTime
属性,这使得它不同于标准 Spring 数据转换器。实现如下(Java 17语法中的instanceof
):
class CustomZonedDateTimeConverter implements PropertyValueConverter {
private final DateTimeFormatter formatterWithZone = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX");
private final DateTimeFormatter formatterWithoutZone = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSS");
@Override
public Object write(Object value) {
if (value instanceof ZonedDateTime zonedDateTime) {
return formatterWithZone.format(zonedDateTime);
} else {
return value;
}
}
@Override
public Object read(Object value) {
if (value instanceof String s) {
try {
return formatterWithZone.parse(s, ZonedDateTime::from);
} catch (Exception e) {
return formatterWithoutZone.parse(s, LocalDateTime::from).atZone(ZoneId.of("UTC"));
}
} else {
return value;
}
}
}
写的部分很简单,写划日期时间就可以了。在阅读时,我首先尝试解析分区日期时间。如果失败,我会尝试在没有任何区域的情况下进行解析。成功后,我将区域设置为 UTC 和 return 值。
如果需要,您可以将其扩展到更多不同的格式。