我正在努力使用 Hibernate Search 6.0.2 将 jsonB 列索引到 Elasicsearch 后端
这是我的实体:
@Data
@NoArgsConstructor
@Entity
@Table(name = "examples")
public class Example {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
@NotNull
@Column(name = "fields")
@Type(type = "jsonb")
private Map<String, Object> fields;
}
这是我针对 Hibernate 搜索的 elasticsearch 后端的编程映射:
@Configuration
@RequiredArgsConstructor
public class ElasticsearchMappingConfig implements HibernateOrmSearchMappingConfigurer {
private final JsonPropertyBinder jsonPropertyBinder;
@Override
public void configure(HibernateOrmMappingConfigurationContext context) {
var mapping = context.programmaticMapping();
var exampleMapping = mapping.type(Example.class);
exampleMapping.indexed();
exampleMapping.property("fields").binder(jsonPropertyBinder);
}
}
我的自定义属性绑定器实现基于 Hibernate Search 6.0.2 文档。
@Component
public class JsonPropertyBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
var userMetadataField = schemaElement.objectField("metadata");
context.bridge(Map.class, new Bridge(userMetadataField.toReference()));
}
@RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference fieldReference;
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
var map = target.addObject(fieldReference);
((Map<String, Object>) bridgedElement).forEach(map::addValue);
}
}
}
我知道文档为 Map 中的对象定义了多个模板(如 MultiTypeUserMetadataBinder 示例),但我真的不知道里面有什么。据我所知,这是一个有效的 json,我的目标是将其作为“字段”下的有效 json 结构放入 Elasticsearch:{...}
在我的例子中,jsonB 列可能包含这样的内容:
{
"testString": "298",
"testNumber": 123,
"testBoolean": true,
"testNull": null,
"testArray": [
5,
4,
3
],
"testObject": {
"testString": "298",
"testNumber": 123,
"testBoolean": true,
"testNull": null,
"testArray": [
5,
4,
3
]
}
但它抛出异常:
org.hibernate.search.util.common.SearchException: HSEARCH400609: Unknown field 'metadata.testNumber'.
我还在我的Spring应用程序中将dynamic_mapping设置为true:
...
spring.jpa.properties.hibernate.search.backend.hosts=127.0.0.3:9200
spring.jpa.properties.hibernate.search.backend.dynamic_mapping=true
...
还有其他想法可以解决这个问题吗?或者也许我在某个地方犯了错误?
我知道文档为 Map 中的对象定义了多个模板(如 MultiTypeUserMetadataBinder 示例),但我真的不知道里面有什么。据我所知,这是一个有效的 json,我的目标是将其作为“字段”下的有效 json 结构放入 Elasticsearch:{...}
如果您不知道每个字段的类型是什么,Hibernate Search 将无法提供太大帮助。如果您确实想将其填充到索引中,我建议声明一个 native field 并按原样推送 JSON。但是,除了使用native JSON之外,您将无法轻松地将谓词应用于元数据字段。
类似这样的:
@Component
public class JsonPropertyBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
// CHANGE THIS
IndexFieldReference<JsonElement> userMetadataField = schemaElement.field(
"metadata",
f -> f.extension(ElasticsearchExtension.get())
.asNative().mapping("{\"type\": \"object\", \"dynamic\":\"true\"}");
)
.toReference();
context.bridge(Map.class, new Bridge(userMetadataField));
}
@RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private static final Gson GSON = new Gson();
private final IndexFieldReference<JsonElement> fieldReference;
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
// CHANGE THIS
target.addValue(fieldReference, GSON.toJsonTree(bridgedElement));
}
}
}
或者,您可以将所有字段声明为字符串。然后 Hibernate Search 在字符串类型上提供的所有功能都将可用。但是,当然,范围谓词或排序之类的东西会导致数值出现奇怪的结果(
2
在10
之前,但"2"
在之后"10"
)。
类似这样的:
@Component
public class JsonPropertyBinder implements PropertyBinder {
@Override
public void bind(PropertyBindingContext context) {
context.dependencies().useRootOnly();
var schemaElement = context.indexSchemaElement();
var userMetadataField = schemaElement.objectField("metadata");
// ADD THIS
userMetadataField.fieldTemplate(
"userMetadataValueTemplate_default",
f -> f.asString().analyzer( "english" )
);
context.bridge(Map.class, new Bridge(userMetadataField.toReference()));
}
@RequiredArgsConstructor
private static class Bridge implements PropertyBridge<Map> {
private final IndexObjectFieldReference fieldReference;
@Override
public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
var map = target.addObject(fieldReference);
// CHANGE THIS
((Map<String, Object>) bridgedElement).forEach(entry -> map.addValue( entry.getKey(), String.valueOf(entry.getValue())));
}
}
}