Search code examples
spring-boothibernatehibernate-searchelasticsearch-7hibernate-search-6

What is replacement for FullTextQuery.setCriteriaQuery() in Hibernate Search 6?


I am migrating Hibernate Search 5 to Hibernate Search 6. Though, the documentation is really helpful, I am not able to find alternative for criteria query in Hibernate Search 6 and didn't quite get from documentation.

This is the Hibernate Search 5 query that I am trying to convert,

final Criteria criteria = entityManager.unwrap(Session.class).createCriteria(KnowledgeData.class);
    criteria.add(Restrictions.eq("deleted", knowledgeSearchRequest.isDeleted()));
    if (knowledgeSearchRequest.isPublished()) {
        criteria.add(Restrictions.eq("published", knowledgeSearchRequest.isPublished()));
    }
    if (!allDesk) {
        criteria.add(Restrictions.eq("deskId", deskId));
        knowledgeSearchRequest.setDesk(deskId);

    } else {
        Disjunction orJunction = Restrictions.disjunction();
        for (String desk : knowledgeSearchRequest.getDeskIds()) {
            orJunction.add(Restrictions.eq("deskId", desk));
        }
        criteria.add(orJunction);
    }
    if (knowledgeSearchRequest.getLang() != null && knowledgeSearchRequest.getLang().size() > 0) {
        criteria.createAlias("language", "lan");
        Disjunction disJunction = Restrictions.disjunction();
        for (String lang : knowledgeSearchRequest.getLang()) {
            disJunction.add(Restrictions.eq("lan.elements", lang));
        }
        criteria.add(disJunction);
    }
    if (knowledgeSearchRequest.getTags() != null && knowledgeSearchRequest.getTags().size() > 0) {
        criteria.createAlias("tags", "tag");
        Disjunction disJunction = Restrictions.disjunction();
        for (String tag : knowledgeSearchRequest.getTags()) {
            disJunction.add(Restrictions.eq("tag.elements", tag));
        }
        criteria.add(disJunction);
    }
    criteria.add(Restrictions.ne("dataType", DataType.FOLDER));
    // if (userProvider.getCurrentUser().isSystemUser() || visibleToUser) {
    final List<DataVisibility> visibility = new ArrayList<>();
    visibility.add(DataVisibility.PUBLIC);
    if (knowledgeSearchRequest.isAddCpUserDocs()) {
        visibility.add(DataVisibility.ALL_USERS_OF_CUSTOMER_PORTAL_ONLY);
    }
    if (knowledgeSearchRequest.isIncludeCpDocs()) {
        visibility.add(DataVisibility.CUSTOMER_PORTAL);
        visibility.add(DataVisibility.ALL_SIGNED_IN_USERS_OF_CUSTOMER_PORTAL_ONLY);
        visibility.add(DataVisibility.ALL_USERS_OF_CUSTOMER_PORTAL_ONLY);
    }
    criteria.add(Restrictions.in("visibility", visibility));
    // }
    if (knowledgeSearchRequest.isPublished()) {
        final long now = System.currentTimeMillis();
        criteria.add(Restrictions.or(
                Restrictions.and(Restrictions.isNotNull("validFrom"), Restrictions.lt("validFrom", now)),
                Restrictions.isNull("validFrom")));
        criteria.add(Restrictions.or(
                Restrictions.and(Restrictions.isNotNull("validTo"), Restrictions.gt("validTo", now)),
                Restrictions.isNull("validTo")));

    }

And, the predicate that i have built so far is,

searchPredicateFactory.bool(
                            f -> f.should(searchPredicateFactory.phrase().field(KnowledgeData.STANDARD_FIELD_NAME_NAME).boost(3)
                                    .field(KnowledgeData.STANDARD_FIELD_NAME_DISPLAY_NAME).boost(3)
                                    .field("description").boost(2).field("content").matching(resultantQuery))
                                    .should(searchPredicateFactory.wildcard().field(KnowledgeData.STANDARD_FIELD_NAME_NAME).boost(3)
                                            .field(KnowledgeData.STANDARD_FIELD_NAME_DISPLAY_NAME).boost(3)
                                            .field("description").boost(2).field("content").matching(resultantQuery))).toPredicate();

Any leads are appreciated.


Solution

  • This is the Hibernate Search 5 query that I am trying to convert,

    I'll nitpick a bit: this is not a Hibernate Search 5 query, this is a Hibernate (ORM) Criteria query. Those restrictions are executed against the database, not against the search indexes.

    From the title of your question, I'll assume you are adding those restrictions to your Hibernate Search query using FullTextQuery.setCriteriaQuery(). Be aware that the documentation in Hibernate Search 5 states "using restriction (ie a where clause) on your Criteria query should be avoided" and the javadoc goes even further by stating "No where restriction can be defined".

    Regardless... it seems it used to work in Hibernate Search 5, at least in some cases.

    Now, to migrate this to Hibernate Search 6+, there is a detailed migration guide, with a section specifically about your problem:

    Hibernate Search 6 does not allow adding a Criteria object to a search query.

    [...]

    If your goal is to apply a filter expressed by an SQL "where" clause executed in-database, rework your query to project on the entity ID, and execute a JPA/Hibernate ORM query after the search query to filter the entities and load them.

    So in short, do something like this:

    List<Long> ids = Search.session(entityManager).search(MyEntity.class)
            .select(f -> f.id(Long.class))
            .where(f -> ...)
            .fetchHits(20);
    
    criteria.add(Restrictions.in("id", ids));
    
    List<MyEntity> hits = criteria.list();
    

    Note this is only a quick fix: just like setCriteria in Hibernate Search 5, this can perform very badly, plays very badly with pagination, and can result in incorrect hit counts.

    I would recommend indexing the properties you use in your Criteria query, and defining your whole query using Hibernate Search only, so as to avoid running the query once against Elasticsearch and then once again against your database.

    See also https://hibernate.atlassian.net/browse/HSEARCH-3630