【问题标题】:lucene - search in list of objectslucene - 在对象列表中搜索
【发布时间】:2022-01-31 18:55:50
【问题描述】:

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

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

列表示例:

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

identifierType 的属性是枚举类型,所以我实现了一个自定义桥接器,就像在这个 link 中一样。

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

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

【问题讨论】:

    标签: hibernate jpa lucene hibernate-search


    【解决方案1】:

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

    解决方案 1:嵌套文档

    我将展示如何使用 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);
    

    【讨论】:

    • 感谢您的回复^^ 当我按照解决方案 2 操作时,我收到此错误 java.lang.ClassCastException: org.hibernate.collection.internal.PersistentSet cannot be cast to IdentifierEntity,为了解决它,我更新了 set 方法 public void set(String name, Object value, Document document, LuceneOptions luceneOptions) { Set&lt;IdentifierEntity&gt; ids = (Set&lt;IdentifierEntity&gt;) value; if (CollectionUtils.isNotEmpty(ids)) { for(IdentifierEntity id: ids) { luceneOptions.addFieldToDocument(name, toString(id.getIdentifierType(), id.getIdentifierValue()), document); } } }
    • 我收到了这个新错误org.hibernate.search.exception.SearchException: HSEARCH000169: FieldBridge 'IdentifierAsStringBridge' does not have a objectToString method: field 'identifiers_strings' in 'ExerciseEntity' The FieldBridge must be a TwoWayFieldBridge or you have to enable the ignoreFieldBridge option when defining a Query
    • 我更新了我的答案,尤其是桥的代码。
    猜你喜欢
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-05
    • 1970-01-01
    • 2015-06-09
    相关资源
    最近更新 更多