Spring elastic : 嵌套字段的正确使用方法是什么?
Spring elastic : What is the correct way of using nested fields?
我对 ElasticSearch 还很陌生。我正在开展一个项目,我们需要在该项目中搜索包含一组两个对象 (OfferTranslation) 的对象 (Offer)。目标是基于一些 Offer 字段进行研究,但也有很多 OfferTranslation 字段。
这是 类 :
的缩小版
Offer.class (请注意,我用 @Field(type= FieldType.Nested) 对 Set 进行了注释,因此我可以进行嵌套查询,如官方文档中所述):
@org.springframework.data.elasticsearch.annotations.Document(indexName = "offer")
@DynamicMapping(DynamicMappingValue.False)
public class Offer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Field(type = FieldType.Long)
private Long id;
@OneToMany(mappedBy = "offer", targetEntity = OfferTranslation.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnoreProperties(
value = { "pictures", "videos", "owner", "contexts", "offer", "personOfInterests", "followers" },
allowSetters = true
)
@Field(type = FieldType.Nested, store = true)
private Set<OfferTranslation> offersTranslations = new HashSet<>();
}
OfferTranslation.class :
@DynamicMapping(DynamicMappingValue.False)
public class OfferTranslation implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Field(type = FieldType.Long)
private Long id;
@NotNull
@Size(max = 100)
@Column(name = "title", length = 100, nullable = false)
@Field(type = FieldType.Text)
private String title;
@NotNull
@Size(max = 2000)
@Column(name = "summary", length = 2000, nullable = false)
@Field(type = FieldType.Text)
private String summary;
@Size(max = 2000)
@Column(name = "competitor_context", length = 2000)
@Field(type = FieldType.Text)
private String competitorContext;
@NotNull
@Size(max = 2000)
@Column(name = "description", length = 2000, nullable = false)
@Field(type = FieldType.Text)
private String description;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "maturity", nullable = false)
@Field(type = FieldType.Auto)
private RefMaturity maturity;
@ManyToOne
@Field(type = FieldType.Object, store = true)
private RefLanguage language;
@NotNull
@Column(name = "created_at", nullable = false)
@Field(type = FieldType.Date)
private Instant createdAt;
}
预期的行为是我可以这样创建 nestedQueries :
QueryBuilder qBuilder = nestedQuery("offersTranslations",boolQuery().must(termQuery("offersTranslations.language.code",language)), ScoreMode.None);
但我得到的是一个异常:创建查询失败:路径 [offersTranslations] 下的 [nested] 嵌套对象不是嵌套类型
编辑:我可以使用普通查询访问 offersTranslations.language.code(目前这并没有真正打扰我)。但是我还是不太明白
我的映射显示字段 offersTranslations 不是您在上面看到的嵌套类型,但由于我使用了 @Field(type = FieldType.Nested) 我不太理解这种行为。
有人能解释一下吗?
{
"offer" : {
"mappings" : {
"properties" : {
"_class" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"categories" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"criteria" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"id" : {
"type" : "long"
},
"keywords" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"offersTranslations" : {
"properties" : {
"competitorContext" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"createdAt" : {
"type" : "date"
},
"description" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"language" : {
"properties" : {
"code" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"maturity" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"state" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"summary" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"updatedAt" : {
"type" : "date"
}
}
},
"units" : {
"properties" : {
"id" : {
"type" : "long"
}
}
}
}
}
}
}
索引映射是如何创建的?它看起来不像 Spring Data Elasticsearch 写了这个映射。如您所见,缺少 offerTranslations
的嵌套类型,文本字段有一个 .keyword
子字段。这看起来数据被插入到索引中而没有定义映射,因此 Elasticsearch 进行了自动映射。在这种情况下,不使用 @Field
注释的值。
您需要让 Spring Data Elasticsearch 使用映射创建索引。如果索引不存在并且您正在使用 Spring Data Elasticsearch 存储库,或者您需要在应用程序中使用 IndexOperations.createWithMapping
函数,这将自动发生。
我注意到的另一件事:您似乎对不同的 Spring 数据存储使用了相同的实体 class,严重混合了注释。您应该为不同的商店使用不同的实体。
解决步骤:
- 使用 Kibana 确保删除
/_mapping
- 在您的实体 类 中查找您需要的可能位于 @JsonIgnoreProperties
中的对象
- 确保你很乐意加载你的 toMany 关系的属性(否则 elastic 不会为你从未提供的数据创建映射)
- 根据这个小经验,我会说避免使用嵌套字段,我看不出使用它们有任何好处。因此,请检查您是否也属于这种情况!
我对 ElasticSearch 还很陌生。我正在开展一个项目,我们需要在该项目中搜索包含一组两个对象 (OfferTranslation) 的对象 (Offer)。目标是基于一些 Offer 字段进行研究,但也有很多 OfferTranslation 字段。 这是 类 :
的缩小版Offer.class (请注意,我用 @Field(type= FieldType.Nested) 对 Set 进行了注释,因此我可以进行嵌套查询,如官方文档中所述):
@org.springframework.data.elasticsearch.annotations.Document(indexName = "offer")
@DynamicMapping(DynamicMappingValue.False)
public class Offer implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Field(type = FieldType.Long)
private Long id;
@OneToMany(mappedBy = "offer", targetEntity = OfferTranslation.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@JsonIgnoreProperties(
value = { "pictures", "videos", "owner", "contexts", "offer", "personOfInterests", "followers" },
allowSetters = true
)
@Field(type = FieldType.Nested, store = true)
private Set<OfferTranslation> offersTranslations = new HashSet<>();
}
OfferTranslation.class :
@DynamicMapping(DynamicMappingValue.False)
public class OfferTranslation implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Field(type = FieldType.Long)
private Long id;
@NotNull
@Size(max = 100)
@Column(name = "title", length = 100, nullable = false)
@Field(type = FieldType.Text)
private String title;
@NotNull
@Size(max = 2000)
@Column(name = "summary", length = 2000, nullable = false)
@Field(type = FieldType.Text)
private String summary;
@Size(max = 2000)
@Column(name = "competitor_context", length = 2000)
@Field(type = FieldType.Text)
private String competitorContext;
@NotNull
@Size(max = 2000)
@Column(name = "description", length = 2000, nullable = false)
@Field(type = FieldType.Text)
private String description;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "maturity", nullable = false)
@Field(type = FieldType.Auto)
private RefMaturity maturity;
@ManyToOne
@Field(type = FieldType.Object, store = true)
private RefLanguage language;
@NotNull
@Column(name = "created_at", nullable = false)
@Field(type = FieldType.Date)
private Instant createdAt;
}
预期的行为是我可以这样创建 nestedQueries :
QueryBuilder qBuilder = nestedQuery("offersTranslations",boolQuery().must(termQuery("offersTranslations.language.code",language)), ScoreMode.None);
但我得到的是一个异常:创建查询失败:路径 [offersTranslations] 下的 [nested] 嵌套对象不是嵌套类型
编辑:我可以使用普通查询访问 offersTranslations.language.code(目前这并没有真正打扰我)。但是我还是不太明白
我的映射显示字段 offersTranslations 不是您在上面看到的嵌套类型,但由于我使用了 @Field(type = FieldType.Nested) 我不太理解这种行为。 有人能解释一下吗?
{
"offer" : {
"mappings" : {
"properties" : {
"_class" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"categories" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"criteria" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"id" : {
"type" : "long"
},
"keywords" : {
"properties" : {
"id" : {
"type" : "long"
}
}
},
"offersTranslations" : {
"properties" : {
"competitorContext" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"createdAt" : {
"type" : "date"
},
"description" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"language" : {
"properties" : {
"code" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
},
"maturity" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"state" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"summary" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"updatedAt" : {
"type" : "date"
}
}
},
"units" : {
"properties" : {
"id" : {
"type" : "long"
}
}
}
}
}
}
}
索引映射是如何创建的?它看起来不像 Spring Data Elasticsearch 写了这个映射。如您所见,缺少 offerTranslations
的嵌套类型,文本字段有一个 .keyword
子字段。这看起来数据被插入到索引中而没有定义映射,因此 Elasticsearch 进行了自动映射。在这种情况下,不使用 @Field
注释的值。
您需要让 Spring Data Elasticsearch 使用映射创建索引。如果索引不存在并且您正在使用 Spring Data Elasticsearch 存储库,或者您需要在应用程序中使用 IndexOperations.createWithMapping
函数,这将自动发生。
我注意到的另一件事:您似乎对不同的 Spring 数据存储使用了相同的实体 class,严重混合了注释。您应该为不同的商店使用不同的实体。
解决步骤:
- 使用 Kibana 确保删除
/_mapping - 在您的实体 类 中查找您需要的可能位于 @JsonIgnoreProperties 中的对象
- 确保你很乐意加载你的 toMany 关系的属性(否则 elastic 不会为你从未提供的数据创建映射)
- 根据这个小经验,我会说避免使用嵌套字段,我看不出使用它们有任何好处。因此,请检查您是否也属于这种情况!