如何在 Spring Data Elasticsearch 中为临时访问器的 HashMap 提供字段类型

How to provide Field type for HashMap of temporal accessors in Spring Data Elasticsearch

给出

private Map<String, ZonedDateTime> timePoints = new HashMap<>();

如何提示spring-data字段的类型?

当直接放在字典中时,如果键和值是日期字符串,转换器会尝试将它们一起解析。

@Field(FieldType.Date)
private Map<String, ZonedDateTime> timePoints = new HashMap<>();

当没有提供字段类型时,会出现以下错误:

Type .. of property .. is a TemporalAccessor class but has neither a @Field annotation defining the date type nor a registered converter for writing! It will be mapped to a complex object in Elasticsearch!

不知道我是否 100% 理解您的问题,但您应该尝试实施 Persistable 并添加带注释的属性,这些值将为存储在 Elasticsearch 中的实体自动维护,如下例所示:

@Document(indexName = "person")
public class Person implements Persistable<Long> {

    @Nullable @Id
    private Long id;

    @CreatedDate
    @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Instant created;

    @Nullable @CreatedBy
    private String createdBy;

    @LastModifiedDate
    @Nullable @Field(type = FieldType.Date, format = DateFormat.basic_date_time)
    private Instant lastModified;

    @Nullable @LastModifiedBy
    private String lastModifiedBy;

    @Nullable
    public Long getId() { return id; }

    @Override
    public boolean isNew() { return id == null || (createdBy == null && created == null); }

    // other properties, getter, setter...
}

documentation 看来,您可能必须创建自定义转换器并将它们添加到自定义 ElasticsearchCustomConversions bean。以下转换器似乎有效:

    @Bean
    public ElasticsearchCustomConversions elasticsearchCustomConversions() {
        return new ElasticsearchCustomConversions(
                Arrays.asList(new ZonedDateTimeWriteConverter(), new ZonedDateTimeReadConverter()));
    }
    
    @WritingConverter
    static class ZonedDateTimeWriteConverter implements Converter<ZonedDateTime, String> {

        @Override
        public String convert(ZonedDateTime source) {
            return DateTimeFormatter.ISO_ZONED_DATE_TIME.format(source);
        }
    }

    @ReadingConverter
    static class ZonedDateTimeReadConverter implements Converter<String, ZonedDateTime> {

        @Override
        public ZonedDateTime convert(String source) {
            return ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse(source));
        }
    }

将注释放在 属性 赞

@Field(FieldType.Date)
private Map<String, ZonedDateTime> timePoints = new HashMap<>();

无法工作,因为 Map 不是时间类型,因此不能这样转换。

如果您离开注释,Map<String, ZonedDateTime> 将被解释为一个对象。例如,如果您有

Map<String, ZonedDateTime> map = new Map();
map.put("utc", ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("UTC")));
map.put("paris", ZonedDateTime.of(LocalDateTime.now(), ZoneId.of("Europe/Paris")));

然后在使用 Spring Data Elasticsearch 存储此对象时,这将尝试创建一个对象以发送到 Elasticsearch(JSON 表示),如下所示:

{
  "utc": {
    
  },
  "paris": {
    
  }
}

应表示时间的内部对象存储为嵌套对象而不是某些转换值,因为无法将字段类型添加到地图的值中 - 您会在日志中看到有关的警告.

但是在 Elasticsearch 中使用 Map 作为 属性 无论如何都是有问题的。这些键被解释为子对象的属性。之前不可能在索引中定义类型的映射,因为不知道这些属性可以有哪些可能的名称。在我的示例中,它是“utc”和“paris”,但它可以是任何字符串。这些中的每一个 Elasticsearch 会将值作为动态映射字段添加到索引中。这可能会导致称为 mapping explosion, therefore Elasticsearch limits the number of fields in an index to a default value of 1000 的东西。您可以重新考虑在 Elasticsearch 中存储数据的方式。

如果您想坚持使用 Map,您需要编写一个自定义转换器,能够将您的 Map<String, ZonedDateTime> 转换为 Map<String, String> 并返回。