Search code examples
hibernatelucenehibernate-search

Hibernate search multiple entities


Is it possible to search multiple entities for the same keyword, filtering each type with a specific filter and getting them in a single list?

For example having the entities, breeder and animal and searching their data for the terms "Boston Terrier". After that the breeders are filtered for location and animals for species and a single list is returned containing both Animals and Breeders.

This list can then be trimmed to enable pagination of the results in the front end of a web-application:

        // FullTextQuery ftq = .... definiing and filtering the query

        int numResults = ftq.getResultSize();
        ftq.setFirstResult((pageNum-1)*numPerPage);
        ftq.setMaxResults(numPerPage);

        List<Object> results = ftq.getResultList();

Solution

  • Assuming you don't care about the relation between breeders and animals, and just want to search for animals OR breeders: yes, you can.

    But there are some conditions:

    • the fields you will apply your "Boston Terrier" term filter on must be configured the exact same way in the Animal class and the Breeder class: same analyzer in particular. Otherwise, your search results will probably be inconsistent. The fields do not have to exist in both indexes: filtering on an "animalName" and "breederName" fields should work fine.
    • the location field you want to use to filter the breeders by location must not exist in the Animal class. Otherwise, the animals will be filtered by location too.
    • the species field you want to use to filter the animals by species must not exist in the Breeder class. Otherwise, the breeders will be filtered by species too.

    If the answer to all three items above is "yep, I'm good", then you can do the following.


    EDIT: Nowadays I would strongly recommend upgrading to Hibernate Search 6.0 or above, which makes searching multiple indexes in the same query much easier.

    In fact, you don't have to do anything specific, except listing the targeted types.

    The snippet from my original answer, converted to Hibernate Search 6, would look something like this:

    EntityManager em = ...;
    List<Object> hits = Search.session(em)
            .search(Arrays.asList(Animal.class, Breeder.class))
            .where(f -> f.bool()
                    .must(f.simpleQueryString()
                            .fields("animalName",
                                    "breeder.company.legalName",
                                    "breeder.firstName",
                                    "breeder.lastName")
                            .matching("Boston Terrier")
                            .defaultOperator(BooleanOperator.AND))
                    .must(f.bool()
                            .should(f.spatial().within()
                                   .field("breederLocation")
                                   .circle(42.0, 42.0,
                                            50, DistanceUnit.KILOMETERS))
                            .should(f.match()
                                   .field("species")
                                   .matching(<some species>)))
            .fetchHits( 20 );
    

    ORIGINAL SOLUTION, for Hibernate Search 5:

    • create a QueryBuilder for Animal, say animalQueryBuilder
    • create another QueryBuilder for Breeder, say breederQueryBuilder
    • use the animalQueryBuilder for the species filter.
    • use the breederQueryBuilder for the location filter
    • use one builder or the other (it shouldn't matter) for the term filter

    Then, when creating the query, pass both the Animal class and the Breeder class to the createFullTextQuery method.

    The query should return both animals and breeders.

    Something like that:

    QueryBuilder animalQueryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity( Animal.class ).get();
    QueryBuilder breederQueryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity( Breeder.class ).get();
    
    Query locationQuery = breederQueryBuilder.spatial()
        .onField( "breederLocation" )
        .within( 10.0, DistanceUnit.KM )
        .ofLatitude( 42.0 ).andLongitude( 42.0 )
        .createQuery();
    Query speciesQuery = animalQueryBuilder.keyword()
        .onField( "species" )
        .matching( <some species> );
    Query termsQuery = animalQueryBuilder.simpleQueryString()
        .onFields(
            "animalName", "breeder.company.legalName",
            "breeder.firstName", "breeder.lastName"
        )
        .withAndAsDefaultOperator()
        .matching( "Boston Terrier" );
    
    Query booleanQuery = animalQueryBuilder.bool()
        .must( termsQuery )
        .must( animalQueryBuilder.bool()
            .should( locationQuery )
            .should( speciesQuery )
            .createQuery()
        )
        .createQuery();
    
    FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery( booleanQuery, Animal.class, Breeder.class );