Search code examples
hibernate-search

Hibernate Search 6 Scrollable returning results in a strange way


Since we upgraded from Hibernate Search 5.11 to Hibernate Search 6 we are having problems with Scrollable results

When we get a chunk of hits from the SearchScroll object each hit is stored in an Arrays.ArrayList

What we expected is that each chunk hits would be an ArrayList of say for example of type long

What we get is an ArrayList where where each hit is an Arrays.ArrayList with the Long value

Current code

SearchScroll scroll = searchSession
            .search(scope)
            .select(projectionArray)
            .where(searchPredicate)
            .sort(getSort(resultType))
            .scroll(20);

Old code with Hibernate Search 5

FullTextQuery fullTextQuery = fullTextSession
            .createFullTextQuery(query, resultType)
            .setSort(getSort(resultType));


    fullTextQuery.setProjection(fields);

    ScrollableResults scrollableResults = fullTextQuery.scroll();

Any suggestions welcome

At worst we can loop through the results and convert the Arrays.ArrayList item to a long but cannot find a way to make that work either

The acual search results are correct just coming back in a different format that what we expect

Changing the code to

SearchScroll<Long> scroll = searchSession
        .search(scope)
        .select(projectionArray)
        .where(searchPredicate)
        .sort(getSort(resultType))
        .scroll(20);

Makes no difference which seems to match the example in the docs

try ( SearchScroll<Book> scroll = searchSession.search( 
                                      Book.class )
    .where( f -> f.matchAll() )
    .scroll( 20 ) ) { 
        for ( SearchScrollResult<Book> chunk = scroll.next(); 
        chunk.hasHits(); chunk = scroll.next() ) { 
            for ( Book hit : chunk.hits() ) { 
        // ... do something with the hits ...
            }

            totalHitCount = chunk.total().hitCount(); 

            entityManager.flush(); 
            entityManager.clear(); 
        }
 }

Not sure if the projection is what is causing the problem

Tested further if I remove the projection I get the results as an ArrayList of the object as expected so obviously I am doing something wrong with the use of projections in Hibernate Search 6

Results without projection

Results with projection

Without projection everything is good

With projection the results are Arrays.ArrayList


Solution

  • If I understand correctly, you are surprised that you get a List for each hit instead of just a Long.

    First, I would recommend that you don't ignore raw type warnings. I'll wager that your projectionArray is defined this way:

    SearchProjection[] projectionArray = new SearchProjection[1];
    

    That's wrong because you're using a "raw" type for SearchProjection, which basically disables all kinds of type-checking for all the code afterwards.

    The correct way of defining that array is as follows:

    SearchProjection<?>[] projectionArray = new SearchProjection<?>[1];
    

    If you do that, then you'll get a compile-time error with the following code, telling you something like "cannot convert SearchScroll<List<?>> to SearchScroll<Long>":

    SearchScroll<Long> scroll = searchSession
            .search(scope)
            .select(projectionArray)
            .where(searchPredicate)
            .sort(getSort(resultType))
            .scroll(20);
    

    Now, the reason you're getting a SearchScroll<List<?>> is you're passing an array of projections to .select(), so you're calling this method from SearchQuerySelectStep:

        SearchQueryWhereStep<?, List<?>, LOS, ?> select(SearchProjection<?>... projections);
    

    This method takes an array of projections as an argument, and (ultimately) returns a query whose hits are lists, with the results of requested projections in the same order as your array of projections.

    You want to call that method instead:

        <P> SearchQueryWhereStep<?, P, LOS, ?> select(SearchProjection<P> projection);
    

    That method takes a single projection as an argument, and (ultimately) returns a query whose hits are directly the result of the requested projection.

    To call that method, pass a single projection instead of an array of projections; then you will get the Long values you expect instead of Lists:

    SearchProjection<Long> projection = ...;
    SearchScroll<Long> scroll = searchSession
            .search(scope)
            .select(projection)
            .where(searchPredicate)
            .sort(getSort(resultType))
            .scroll(20);