无法在 Spring 数据弹性搜索中转换属性'createdTime'的值'2022-01-30T20:44:43.786438'



我最近更新到了spring data elasticsearch的最新版本。当我试图保存或读取数据时,我总是会收到以下错误,

无法转换属性的值"2022-01-30T20:44:43.786438"'创建时间'引起原因:java.time.temporal.UnsupportedTemporalTypeException:不支持的字段:OffsetSeconds

以前在旧版本中,我使用以下

@Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
LocalDateTime createdTime;

所以我现在尝试了以下方法。但在将数据保存到弹性搜索时,它再次抛出了相同的上述错误。用这种方式读取数据很好。

@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核心库中的一些类,这些类进行了大量的自定义处理和回退。我们现在不能再使用这些了,所以我们使用JDK中的标准DateTimeFormatter和定义的模式,这有时不太灵活。

编辑31.01.2022:

正如评论中所说,Elasticsearch返回的字符串不仅不包含区域信息,还有存在区域信息的情况。

因此,当您需要准备一个没有区域信息的值时,您将需要一个LocalDateTime,当有可用的区域信息时,您需要一个ZonedDateTime。没有现成的解决方案,只使用@Field注释参数,但正如您所写的,您使用的是最新版本的Spring Data Elasticsearch,应该是4.3。由于这个版本,您可以为单个属性定义自定义转换器(而不是为整个类只定义一次(。

让我展示一下如何做到这一点:

我将createdTime属性的类更改为ZonedDateTime,当读取没有区域信息的数据时,我会将其设置为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注释导致创建给定类的转换器(必须具有无参数构造函数(并将其附加到此属性。它不会用于应用程序中的任何其他ZonedDateTime属性,这使它不同于标准的Spring数据转换器。实现如下(instanceof中的Java 17语法(:

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并返回值。

如果需要的话,你可以将其扩展到更多不同的格式

最新更新