在 Spring Data Elasticsearch 中,有一种方法可以检测索引上的映射与从实体对象创建的映射不匹配吗?
即 我允许 Spring Data Elasticsearch 最初从用
@Document
和 @Field
注释的实体模型创建映射
稍后,我向模型添加一个新字段,但不更新索引映射,这个新字段将无法正确配置,直到我重新索引到新映射为止
那么有没有一种方法可以检测此类差异,以便我知道哪些索引需要重新创建映射并重新索引文档?
这是一个有趣的问题,但答案并不太复杂。
假设我们有一个
Foo
实体类存储在 foo
索引中,并且有一个 FooRepository
使用该类。在
应用程序启动时,当索引不存在时,将使用从实体派生的映射来创建索引。
为了检测从类派生的映射和存储在 Elasticsearch 中的映射的变化,您可以使用 像这样的方法:
@Component
public class FooMappingValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(FooMappingValidator.class);
private final ObjectMapper objectMapper = new ObjectMapper();
private final ElasticsearchOperations operations;
public FooMappingValidator(ElasticsearchOperations operations) {
this.operations = operations;
}
@Autowired
public void checkFooMapping() {
var indexOperations = operations.indexOps(Foo.class);
if (indexOperations.exists()) {
LOGGER.info("checking if mapping for Foo changed");
var mappingFromEntity = indexOperations.createMapping();
var mappingFromEntityNode = objectMapper.valueToTree(mappingFromEntity);
var mappingFromIndexNode = objectMapper.valueToTree(indexOperations.getMapping());
if (!mappingFromEntityNode.equals(mappingFromIndexNode)) {
LOGGER.info("mapping for class Foo changed!");
indexOperations.putMapping(mappingFromEntity);
}
}
}
}
这是一个已注入
ElasticsearchOperations
的 Spring 组件,并且有一个注释为的方法
@Autowired
。在将所有依赖项注入到 Bean 中后,将执行自动装配方法。这意味着
它在正常应用程序逻辑启动之前运行。
在此方法中,我们首先获得实体类的
IndexOperations
实例。接下来我们检查索引是否存在,
如果没有,我们不需要检查。
在下一步中,我们从实体获取当前映射并将其转换为
JsonNode
并对
我们从 Elasticsearch 检索的映射。我们在这里使用 JsonNode
是因为它们有一个 equals()
方法
做我们需要的比较。
如果我们检测到两个映射不同,我们将使用
putMapping()
方法将新映射写入索引。
注意:
这仅在向实体添加新属性时才有效,因为无法在 Elasticsearch 中更改现有映射, 在那里你需要重新索引。
这是一种替代解决方案,可以承受模型排序顺序的变化。 请注意,只有当您使用 ElasticSearch / OpenSearch 中的实际类型注释每个模型字段时,这才真正有效,如下所示:
@Field(type = Long)
var coldRent: Int? = null
如果您没有显式指定实际类型,则会将其设置为
Type.Auto
,我认为仅在运行时才会解析为其 actual 类型。
Kotlin 中的示例代码:
val indexOperations = elasticsearchOperations.indexOps(MyModel::class.java)
if (indexOperations.exists()) {
val existingMapping = (indexOperations.mapping["properties"] as Map<String, *>).keys
val mappingCandidate = (indexOperations.createMapping()["properties"] as Map<String, *>).keys
val missingOnRemote = mappingCandidate.filter { !existingMapping.contains(it) }
if (missingOnRemote.isNotEmpty()) {
LOGGER.i { "Mapping is out of sync. Missing fields on remote and thus, updating mapping: $missingOnRemote" }
indexOperations.putMapping(indexOperations.createMapping())
indexOperations.refresh()
} else {
LOGGER.w { "Mapping is up to date." }
}
}