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

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.

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

Related

How to match numeric and boolean values in a lucene query

I am using hibernate search to construct a lucene query that returns string values that contain (part of) the search string. Next to that the query must only return the string values if the language id matches as well and if the deleted flag isn't set to true. I've made the below code for this. But the problem is that it doesn't return anything.
private Query getQueryWithBooleanClauses(Class entityClass, String searchString, Long preferredLanguageId, FullTextEntityManager fullTextEntityManager, String firstField, String... additionalFields) {
QueryBuilder queryBuilder = getQueryBuilder(entityClass, fullTextEntityManager);
Query containsSearchString = getMatchingStringCondition(searchString, queryBuilder, firstField, additionalFields);
BooleanQuery isPreferredOrDefaultLanguageTranslation = getLanguageCondition(preferredLanguageId);
BooleanQuery finalQuery = new BooleanQuery.Builder()
.add(new TermQuery(new Term("parentDeleted", "false")), BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("parentApproved", "true")), BooleanClause.Occur.MUST)
.add(new TermQuery(new Term("childDeleted", "false")), BooleanClause.Occur.MUST)
.add(isPreferredOrDefaultLanguageTranslation, BooleanClause.Occur.MUST)
.add(containsSearchString, BooleanClause.Occur.MUST)
.build();
return finalQuery;
}
getMatchingStringCondition
private Query getMatchingStringCondition(String searchString, QueryBuilder queryBuilder, String firstField, String... additionalFields) {
log.info(MessageFormat.format("{0}*", searchString));
return queryBuilder.simpleQueryString()
.onFields(firstField, additionalFields)
.withAndAsDefaultOperator()
.matching(MessageFormat.format("{0}*", searchString))
.createQuery();
}
getLanguageCondition
private BooleanQuery getLanguageCondition(Long preferredLanguageId) {
return new BooleanQuery.Builder()
.add(createLanguagePredicate(preferredLanguageId), BooleanClause.Occur.SHOULD)
.add(createLanguagePredicate(languageService.getDefaultLanguage().getId()), BooleanClause.Occur.SHOULD)
.build();
}
createLanguagePredicate
private Query createLanguagePredicate(Long languageId){
return new TermQuery(new Term("language.languageId", languageId.toString()));
}
Query executing method
public List<AutoCompleteSuggestion> findAllBySearchStringAndDeletedIsFalse(Class entityClass, String searchString, Long preferredLanguageId){
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
Query finalQuery = getQueryWithBooleanClauses(entityClass, searchString, preferredLanguageId, fullTextEntityManager, "parent.latinName", "translatedName");
FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery(finalQuery, entityClass);
fullTextQuery.setProjection("parentId", "autoCompleteSuggestion", "childApproved"); //volgorde moet overeen komen met argumenten volgorde in AutoCompleteSuggestion constructor, zie convertToAutoCompleteSuggestionList
fullTextQuery.setMaxResults(maxResults);
fullTextQuery.getResultList();
return convertToAutoCompleteSuggestionList(fullTextQuery.getResultList());
}
This code doesn't throw an error but never returns anything either. Only when i remove all the boolean conditions for the boolean and numerical fields, leaving only the containsSearchString condition will the query return anything.
According to this post Hibernate Search 5.0 Numeric Lucene Query HSEARCH000233 issue this happens because as of Hibernate search 5 numerical fields are no longer treated as text fields and you can't perform matching queries on numerical fields.
You can force that the fields are treated as textfields by annotating them with #FieldBridge. But i'd rather not do that. So my question is. How do i perform match queries on non-text fields like booleans, dates, and numbers?
EDIT: It works if i annotate all the fields required for filtering with #FieldBridge(impl= implementation.class)`,also the index parameter must always be set to YES.
But now all these fields will be stored as strings, which is undesirable. So i'd still like to know if there is another more elegant way to apply filters.
EDIT 2:
#yrodiere, When i removed #FieldBridge(impl = LongBridge.class) from languageId and replace the line .add(isPreferredOrDefaultLanguageTranslation, BooleanClause.Occur.MUST) with:
.add(queryBuilder.bool().must(queryBuilder.keyword().onField("language.languageId").matching(languageService.getDefaultLanguage().getId().toString()).createQuery()).createQuery(), BooleanClause.Occur.MUST)
I get the error:
org.hibernate.search.exception.SearchException: HSEARCH000238: Cannot create numeric range query for field 'language.languageId', since values are not numeric (Date, int, long, short or double)
However just now i discovered that matching() also accepts a Long number so i don't have to call toString() on it. When matching() uses the Long value i don't get an error but nothing is returned either.
Only when i used new TermQuery(new Term("language.languageId", languageId.toString())) instead of matching() while also using a LongBridge for languageId will anything get returned. Am i defining the matching() query erroneously?
I also have a different question that i wanted to start a new SO question for. But maybe you can answer that question in this thread as well :). The question is about the includeEmbeddedObjectId parameter of #IndexedEmbedded. I think i know what this does but i would like to have some confirmation from you.
I assume that when i set this to true the id of the parent entity will be included in the lucene document of the child entity, correct? Lets say that this parent entity is used in a matching() query thats used as a true/false condition. Is it then correct to assume that the search will be faster because the id can now also be found in the lucene document of the child entity?
Thanks
Booleans are still indexed as strings in Hibernate Search 5. See org.hibernate.search.bridge.builtin.BooleanBridge. So boolean fields are not part of the problem here.
If you really want to create numeric queries yourself, in Hibernate Search 5 you will have to use numeric range queries, e.g.:
private Query createLanguagePredicate(Long languageId){
return org.apache.lucene.search.NumericRangeQuery.newLongRange("language.languageId", languageId,
languageId, true, true);
}
That being said, to avoid that kind of problems, you should use the Hibernate Search DSL. Then you'll pass values of the type you use in your model (here, a Long), and Hibernate Search will create the right query automatically.
Or even better, upgrade to Hibernate Search 6, which exposes a different API, but less verbose and with fewer quirks. See for yourself in the documentation of the Search DSL in Hibernate Search 6, in particular the predicate DSL.

Spring MongoDB query with or operator and text search

How can i build this MongoDB query with Spring Criteria?
{
$or: [
{ "$text" : { "$search" : "570-11024" } },
{"productDetails.code": "572-R110"}
]
}
It combines a fulltext index search with normal Where criteria with an orOperator.
Query's orOperator(Criteria... criteria) method takes only Criteria and no TextCriteria and also no CriteriaDefinition interface.
Yeah you are right, in spring data mongo you could do this,
final TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny("570-11024");
final DBObject tc = textCriteria.getCriteriaObject();
final Criteria criteria = Criteria.where("productDetails.code").is("572-R110");
final DBObject co = criteria.getCriteriaObject();
BasicDBList or = new BasicDBList();
or.add(tc);
or.add(co);
DBObject qq = new BasicDBObject("$or", or);
// Use MongoTemplate to execute command
mongoTemplate.executeCommand(qq);
Yes, you currently cannot use the Query's orOperator method to combine Criteria and TextCriteria. A workaround involves converting both the Criteria and TextCriteria objects to its Document representations, adding it to a BasicDbList and then converting back to a "$or" Criteria object.
TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matchingAny("570-11024");
Criteria criteria = Criteria.where("productDetails.code").is("572-R110");
BasicDBList bsonList = new BasicDBList();
bsonList.add(criteria.getCriteriaObject());
bsonList.add(textCriteria.getCriteriaObject());
Query query = new Query();
query.addCriteria(new Criteria("$or").is(bsonList));
mongoTemplate.find(query, YourEntity.class);
PS: Someone has raised this issue in the spring-data-mongodb repo with a proposed fix by
changing the parameter types of orOperator from Criteria to CriteriaDefinition.
https://github.com/spring-projects/spring-data-mongodb/issues/3895.

java: how to limit score results in mongo

I have this mongo query (java):
TextQuery.queryText(textCriteria).sortByScore().limit(configuration.getSearchResultSize())
which performs a text search and sort by score.
I gave different wiehgt to different fields in the docuemnt, and now I'd like to retrieve only those results with score lower then 10.
is there a way to add that criteria to the query?
this didn't work:
query.addCriteria(Criteria.where("score").lt(10));
if the only way is to use aggregation - I need a mongoTemplate example for that.
in other words
how the do I translate the following mongo shell aggregate command, to java spring's mongoTemplate command??
can't find anywhere how to use the aggregate's match() API with the $text search component (the $text is indexed on several different fields):
db.text.aggregate(
[
{ $match: { $text: { $search: "read" } } },
{ $project: { title: 1, score: { $meta: "textScore" } } },
{ $match: { score: { $lt: 10.0 } } }
]
)
Thanks!
Please check with below code sample, MongoDB search with pagination code in java
BasicDBObject query = new BasicDBObject()
query.put(column_name, new BasicDBObject("$regex", searchString).append("$options", "i"));
DBCursor cursor = dbCollection.find(query);
cursor.skip((pageNum-1)*limit);
cursor.limit(limit);
Write a loop and and call the above code from loop and pass the values like pageNum starts from 1 to n and limit depends on your requirement. check the cursor is empty or not. If empty skip the loop if not continue calling the above code base.
Hope this will be helpful.

Update data in mongo Db using spring (MongoTemplate)

I have a collection (A) which has two fields (String, integer) in mongoDB. I want to update the collection by adding some value to sting
Ex. Lets say i have a document A[field1 : ABC,field2 : 25]. I want to update it by adding ,say 5, to it so it will look like A[field1 :ABC,field2 : 30] after updation.
The code I have used for this is as follows:
Query query = new Query();
query.addCriteria(Criteria.where("field1").is("ABC));
BeanName beanName = template.findOne(query, BeanName.class,collectionName);
if(null != beanName){
Update update = new Update();
update.set("field1", "ABC");
update.set("field2", beanName.getField2() + 5)
template.updateFirst(query, update, BeanName.class,collectionName);
}
else{
template.save(beanName, collectionName); // the value of filed1 and field 2 is populated in a bean with instance 'beanName'
}
The code is woriking fine with expected results but the performance is very slow. Is there any other efficient way for it.
I am working on large amount of data to update.
I would suggest you to use findAndModify() method, in combination with the upsert = true feature. Please find the official documentation below :
https://docs.mongodb.org/manual/reference/method/db.collection.findAndModify/

Filtered Query in Elasticsearch Java API

I am little bit confused while creating Filtered query in Elasticsearch Java API.
SearchRequestBuilder class has setPostFilter method, javadoc of this method clearly says that filter will be applied after Query is executed.
However, there is no setFilter method Or some other method which will allow to apply filter before
query is executed. How do I create filtered Query(which basically applies filter before query is executed) here? Am I missing something?
FilteredQueryBuilder builder =
QueryBuilders.filteredQuery(QueryBuilders.termQuery("test",
"test"),FilterBuilders.termFilter("test","test"));
It will build the filtered query...To filteredQuery, first argument is query and second arguments is Filter.
Update: Filtered query is depreciated in elasticsearch 2.0+.refer
Hope it helps..!
QueryBuilders.filteredQuery is deprecated in API v. 2.0.0 and later.
Instead, filters and queries have "equal rights". FilterBuilders no longer exists and all filters are built using QueryBuilders.
To implement the query with filter only (in this case, geo filter), you would now do:
QueryBuilder query = QueryBuilders.geoDistanceQuery("location")
.point(center.getLatitude(), center.getLongitude())
.distance(radius, DistanceUnit.METERS);
// and then...
SearchResponse resp = client.prepareSearch(index).setQuery(query);
If you want to query by two terms, you would need to use boolQuery with must:
QueryBuilder query = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("user", "ben"))
.must(QueryBuilders.termQuery("rank", "mega"));
// and then...
SearchResponse resp = client.prepareSearch(index).setQuery(query);
In case you just want to execute filter without query, you can do like this:
FilteredQueryBuilder builder = QueryBuilders.filteredQuery(QueryBuilders.matchAllQuery(),
FilterBuilders.termFilter("username","stackoverflow"));
Ref: Filtering without a query
It seems that by wrapping the query (a BoolQueryBuilder object) by giving it as an argument to boolQuery().filter(..) and then setting that in the setQuery() as you suggested- then this can be achieved. The "score" key in the response is always 0 (though documents are found)
Be careful here! If the score is 0, the score has been calculated. That means the query is still in query context and not in filter context. In filter context the score is set to 1.0 (without calculation) source
To create a query in filter context without calculation of the score (score = 1.0) do the following (Maybe there is still a better way?):
QueryBuilder qb = QueryBuilders.constantScoreQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("name", "blub)));
This returns the same results like:
GET /indexName/typeName/_search
{
"filter": {
"query": {
"bool": {
"must": [
{ "match": {
"name": "blub"
}}
]
}
}
}
}
Since FilteredQueryBuilder is deprecated in the recent versions, one can use the QueryBuilders.boolQuery() instead, with a must clause for the query and a filter clause for the filter.
import static org.elasticsearch.index.query.QueryBuilders.*;
QueryBuilder builder = boolQuery().must(termQuery("test", "test")).filter( boolQuery().must(termQuery("test", "test")));

Resources