Search code examples
hibernate-search

Hibernate Search: searchable and sortable on the same field


I am using hibernate search 5.11.1 and need to support searchable and sortable on the same field of type String. After reading through the reference guide, I found the following documentation about using Normalizers to sort analyzed text.

Reference from the official guide:

Analyzers are great when you need to search in text documents, but what if you want to sort the analyzed text? Then you’re in for a bit of trouble, because analyzed text is multi-valued: when indexing a book with the title "Refactoring: Improving the Design of Existing Code", the analyzed title is in fact the (unordered) set {"refactoring", "improving", "design", "existing", "code"}. If you tried to sort on the title after such an analysis, any of those words could be used, so your book could end up in the D’s (because of "design"), or in the R’s (because of "refactoring"), or in the E’s, etc.

So in the end, you probably don’t want your fields to be tokenized when you sort on those fields. Normalizers solve exactly this issue: they are analyzers, but without a tokenizer, and with some runtime checks that prevent the analysis to produce multiple tokens, thereby ensuring your sorts will always be consistent.

Hibernate Search provides normalizer equivalent for relevant analyzer annotations: @Normalizer, @NormalizerDef, @NormalizerDefs. As with analyzer, you can use implementations directly (for instance @Normalizer(impl = MyCollactionKeyAnalyzer.class)) or named normalizers (for instance @Normalizer(definition = "myNormalizer") with @NormalizerDef(filters = @TokenFilterDef(factory = LowerCaseFilterFactory.class)).

Based on the above, I wrote the following code:

@Entity
@Indexed
@AnalyzerDef(name = "en", tokenizer = @TokenizerDef(factory = StandardTokenizerFactory.class),
        filters = {
                @TokenFilterDef(factory = LowerCaseFilterFactory.class),
                @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class)
        })
@NormalizerDef(name = "lowercase", filters = {
                @TokenFilterDef(factory = ASCIIFoldingFilterFactory.class),
                @TokenFilterDef(factory = LowerCaseFilterFactory.class)
        }
)
@Table(name = "ORDER")
public class Order {

    //Some other fields that are omitted for brevity here

    @Field(name = "orderName_Search", store = Store.YES, analyze = Analyze.YES, analyzer = @Analyzer(definition = "en"))
    @Field(name = "orderName_Sort", store = Store.YES, analyze = Analyze.NO, normalizer = @Normalizer(definition = "lowercase"))
    @SortableField(forField = "orderName_Sort")
    @Column(name = "ORDER_NAME")
    private String orderName;

}

However, it does not seem to work as I ran into the following error.

[ERROR] com.appnexus.konnect.web.exceptions.ConnectExceptionHandler - Exception org.hibernate.search.exception.SearchException: Cannot automatically determine the field type for field 'orderName'. Use byField(String, Sort.Type) to provide the sort type explicitly

My question is where it went wrong? It works when only using either @Field annotation but fails when using both.


Solution

  • From the error message, it seems you wrote something like this:

    qb.sort().byField("orderName").createSort()
    

    But the field orderName does not exist as far as Hibernate Search is concerned. Only orderName_Search and orderName_Sort exist. In this case you should write:

    qb.sort().byField("orderName_Sort").createSort()
    

    Also, be aware that setting analyze = Analyze.NO on orderName_Sort will effectively disable your normalizer. You probably want to leave that out.