使用 @IndexingDependency derivedFrom 和 属性 桥

Using @IndexingDependency derivedFrom and a property bridge

我想将休眠搜索的 @IndexingDependencyPropertyBridge 一起使用,但我似乎无法让它工作。

我收到这个错误:

Hibernate ORM mapping: type 'com.something.Person': path '.currentStatus': 
  failures: 
    - HSEARCH700020: Unable to find the inverse side of the association on type
    'com.something.Person' at path '.currentStatus<no value extractors>'. Hibernate Search
    needs this information in order to reindex 'com.something.Person' when
    'com.something.Status' is modified. You can solve this error by defining the inverse
    side of this association,  either with annotations specific to your integration
    (@OneToMany(mappedBy = ...) in Hibernate ORM)  or with the  Hibernate Search 
    @AssociationInverseSide annotation. Alternatively, if you do not need to reindex 
    'com.something.Person' when 'com.something.Status' is modified, you can disable 
     automatic reindexing with @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)

不确定我是否做错了什么,或者我尝试做的事情是不可能的。感谢您的帮助。

这里是涉及到的文件。

Person.class

@Entity
@Table
@Indexed
public class Person {
    @OneToMany(mappedBy = "patient", cascade = CascadeType.ALL) 
    private Set<Status> status = new HashSet<>();

    @Transient
    @StatusBinding(fieldName = "currentStatus")
    @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "status")))
    public Status getCurrentStatus() {
        return this.status.stream()
            .filter(it -> it.getDate().isAfter(LocalDate.now()))
            .max(Comparator.comparing(Status::getDate))
            .orElse(null);
    }
}

StatusBinding.class

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
@PropertyMapping(processor = @PropertyMappingAnnotationProcessorRef(type = StatusBinding.Processor.class))
@Documented
public @interface StatusBinding {

    String fieldName() default "";

    class Processor implements PropertyMappingAnnotationProcessor<StatusBinding> {
        @Override
        public void process(PropertyMappingStep mapping, StatusBindingannotation, PropertyMappingAnnotationProcessorContext context) {
            StatusBinderbinder = new StatusBinder();
            if (!annotation.fieldName().isBlank()) binder.setFieldName(annotation.fieldName());
            mapping.binder(binder);
        }
    }
}

StatusBinder.class

public class StatusBinder implements PropertyBinder {
    @Setter private String fieldName = "mainStatus";

    @Override
    public void bind(PropertyBindingContext context) {
        context.dependencies()
            .use("status")
            .use("date")
            .use("note");

        IndexSchemaObjectField mainStatusField = context.indexSchemaElement().objectField(this.fieldName);

        context.bridge(Status.class, new StatusBridge(
                mainStatusField.toReference(),
                mainStatusField.field("status", context.typeFactory().asString()).toReference(),
                mainStatusField.field("date", context.typeFactory().asLocalDate()).toReference(),
                mainStatusField.field("note", context.typeFactory().asString()).toReference()
        ));
    }

    private static class StatusBrige implements PropertyBridge<Status> {
        private final IndexObjectFieldReference mainStatusField;
        private final IndexFieldReference<String> statusField;
        private final IndexFieldReference<LocalDate> dateField;
        private final IndexFieldReference<String> noteField;

        public StatusBrige(
                IndexObjectFieldReference mainStatusField,
                IndexFieldReference<String> statusField,
                IndexFieldReference<LocalDate> dateField,
                IndexFieldReference<String> noteField
        ) {
            this.mainStatusField = mainStatusField;
            this.statusField = statusField;
            this.dateField = dateField;
            this.noteField = noteField;
        }

        @Override
        public void write(DocumentElement target, Status mainStatus, PropertyBridgeWriteContext context) {
            DocumentElement statutElement = target.addObject(this.mainStatusField);
            statutElement.addValue(this.statusField, mainStatus.getStatus);
            statutElement.addValue(this.dateField, mainStatus.getDate());
            statutElement.addValue(this.noteField, mainStatus.getNote());
        }
    }
}

问题

Status 实体被修改时,Hibernate Search 不知道如何检索具有 Status 作为其 currentStatus 的相应 Person

解决方案

假设currentStatus总是包含在status中,并且由于Status.patientPerson.status关联的反面,你应该只需要添加:

    @Transient
    @StatusBinding(fieldName = "currentStatus")
    @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "status")))
    // ADD THIS ANNOTATION
    @AssociationInverseSide(
            inversePath = @ObjectPath(@PropertyValue(propertyName = "patient"))
    )
    public Status getCurrentStatus() {
       // ...
    }

为什么?

我会试着解释一下,但有点复杂,请耐心等待。

派生属性和关联的反面是相关概念:它们具有允许 Hibernate Search 执行自动重新索引的共同目的。

但是,它们仍然是不同的概念,Hibernate Search 无法从另一个推断出一个。

使用 @IndexingDependency(derivedFrom),您定义了 currentStatus 的计算依赖于:

    @IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "status")))
    public Status getCurrentStatus() {

这告诉 Hibernate Search,只要 status 属性 发生变化,currentStatus 就会发生变化。有了这些信息,Hibernate Search 就能够确定无论何时调用 person.getStatus().remove(...)person.getStatus().add(...)(例如),您的 Person 实体都需要重新索引,因为 currentStatus 已被索引,并且它可能改变了。

在您的自定义活页夹中,您也是 defining dependencies:

        context.dependencies()
            .use("status")
            .use("date")
            .use("note");

这告诉 Hibernate Search,每当 Status 实体的 statusdatenote 属性发生变化时,Person 具有 Status 因为它的 currentStatus 需要重建索引。

但是...Hibernate Search 不知道的是如何检索将 Status 作为其 currentStatus 的人。

它可能知道如何检索所有在 status 集合中具有 Status 的人,但这是另一回事,不是吗? Hibernate Search 不知道 currentStatus 实际上是 status 属性 中包含的元素之一。就其所知,getCurrentStatus() 很可能会这样做:status.iterator().next().getParentStatus()。那么当前的状态就不会被包含在Person#status中,并且不清楚myStatus.getPatient()是否可以return一个currentStatusmyStatusPerson

所以你需要明确地告诉 Hibernate Search:“从给定的 Status myStatus,如果你检索 myStatus.getPatient() 的值,你会得到 PersoncurrentStatus 属性 可能指向 myStatus”。这正是 @AssociationInverseSide 的用途。