lucene - 在对象列表中搜索

lucene - search in list of objects

我有实体 ExerciseEntity,其中包含一个对象列表 identifiers

@ElementCollection
@IndexedEmbedded(targetElement = IdentifierEntity.class)
private Set<IdentifierEntity> identifiers;

列表示例:

"identifiers": [
    {
      "identifierType": "AM",
      "identifierValue": "333333333"
    },
    {
      "identifierType": "FINESS",
      "identifierValue": "888888888"
    }
]

属性 identifierType 是枚举类型,所以我实现了一个像这样的自定义桥

@Enumerated
@Field(name = "identifierType", bridge = @FieldBridge(impl = EnumAsIntegerBridge.class))
private IdentifierTypeEnum identifierType;

我想通过 identifierValue 搜索所有 ExerciseEntity,其中 identifierType 等于 FINESS。 我尝试了以下查询,但它无法正常工作,当我使用 identifierValue=333333333 进行测试时,我得到了一个结果,但我应该什么也得不到,因为它的类型不是 FINESS。

List<Query> listOfQuery = new ArrayList<>();
listOfQuery.add(getQueryBuilder().keyword().onField("identifiers.identifierType").matching(IdentifierTypeEnum.FINESS.ordinal()).createQuery());
listOfQuery.add(getQueryBuilder().keyword().onField("identifiers.identifierValue").matching(identifierValue).createQuery());
Builder finalLuceneQuery = new BooleanQuery.Builder();
listOfQuery.stream().forEach(query -> finalLuceneQuery.add(query, BooleanClause.Occur.MUST));

FullTextQuery fullTextQuery = getFullTextEntityManager().createFullTextQuery(finalLuceneQuery.build(), ExerciseEntity.class);

有两种解决方法。 Upgrading to Hibernate Search 6.0 或更高版本仅对第一个解决方案是强制性的,但我还是建议升级,因为 Hibernate Search 5.x 不再获得新功能。

解决方案一:嵌套文档

我将展示如何使用 Hibernate Search 6.x 执行此操作,因为 Hibernate Search 5.x.

中没有等效项

在 Hibernate Search 6+ 中,您可以 mark your @IndexedEmbedded as NESTED

@ElementCollection
@IndexedEmbedded(structure = ObjectStructure.NESTED)
private Set<IdentifierEntity> identifiers;

然后 IdentifierEntity 对象的结构将保留在索引中,并且您可以在搜索期间指定您希望两个谓词应用于同一对象,方法是将它们包装在 nested 谓词:

List<ExerciseEntity> hits = searchSession.search( ExerciseEntity.class )
        .where( f -> f.nested().objectField( "identifiers" ) 
                .nest( f.bool()
                        .must( f.match().field( "identifiers.identifierType" )
                                .matching( IdentifierTypeEnum.FINESS ) ) 
                        .must( f.match().field( "identifiers.identifierValue" )
                                .matching( identifierValue ) ) 
                ) )
        .fetchHits( 20 ); 

解决方案 2:两个值的单个字段

有一种技巧可以在不使用嵌套文档的情况下复制上述行为:只需将两个字段合并为一个。

我将展示如何使用 Hibernate Search 5.x 执行此操作,因为您似乎正在使用它,但是使用 Hibernate Search 6.x 可以很好地实现相同的解决方案(使用value bridge).

实施完全符合此要求的自定义网桥:

public class IdentifierAsStringBridge implements MetadataProvidingFieldBridge, StringBridge {
    public static String toString(IdentifierTypeEnum type, String value) {
        return type.name() + ":" + value;
    }

    @Override
    public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
        builder.field( name, FieldType.String );
    }

    @Override
    public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
        if ( value == null ) {
            return;
        }

        Collection<IdentifierEntity> idCollection = (Collection<IdentifierEntity>) value;

        for ( IdentifierEntity id : idCollection ) {
            luceneOptions.addFieldToDocument( name, toString( id.getIdentifierType(), id.getIdentifierValue() ), document );
        }
    }

    @Override
    public String objectToString(Object value) {
        if ( value instanceof String ) {
            return (String) value;
        }
        else if ( value instanceof IdentifierEntity ) {
            IdentifierEntity id = (IdentifierEntity) value;
            return toString( id.getIdentifierType(), id.getIdentifierValue() );
        else {
            throw new IllegalArgumentException( "This bridge only supports passing arguments of type String or IdentifierEntity" );
        }
    }

}

在您的“标识符”集合中使用该新桥:

@ElementCollection
@Field(name = "identifiers_strings", bridge = @FieldBridge(impl = IdentifierAsStringBridge.class))
private Set<IdentifierEntity> identifiers;

最后,更新您的查询以定位您刚刚添加的单个字段:

List<Query> listOfQuery = new ArrayList<>();
listOfQuery.add(getQueryBuilder().keyword()
        .onField("identifiers_strings")
        .matching(IdentifierAsStringBridge.toString(
                IdentifierTypeEnum.FINESS, identifierValue
        ))
        .createQuery());

Builder finalLuceneQuery = new BooleanQuery.Builder();
listOfQuery.stream().forEach(query -> finalLuceneQuery.add(query, BooleanClause.Occur.MUST));

FullTextQuery fullTextQuery = getFullTextEntityManager()
        .createFullTextQuery(finalLuceneQuery.build(), ExerciseEntity.class);