QueryDSL & Hibernate-Search with Lucene Analyzers - java

I configured Hibernate-Search to use my custom analyzer when indexing my entities. However when I try and search with QueryDSL's Hibernate-Search integration, it doesn't find entities, but if I use straight hibernate-search it finds something.
#AnalyzerDef(name = "customanalyzer",
tokenizer = #TokenizerDef(factory = StandardTokenizerFactory.class),
filters = {
#TokenFilterDef(factory = LowerCaseFilterFactory.class),
#TokenFilterDef(factory = SnowballPorterFilterFactory.class, params = {
#Parameter(name = "language", value = "English")
})
})
#Analyzer(definition = "customanalyzer")
public abstract class Post extends BaseEntity {}
I indexed an entity with a title of "the quick brown fox jumped over the lazy dog".
These work…
List articlePosts = fullTextEntityManager.createFullTextQuery(queryBuilder.keyword().onFields("title").matching("jumped").createQuery(), ArticlePost.class).getResultList(); // list of 2
List articlePosts = fullTextSession.createFullTextQuery(queryBuilder.keyword().onFields("title").matching("jumped").createQuery(), ArticlePost.class).getResultList(); // list of 2
This does not…
SearchQuery<ArticlePost> query = new SearchQuery<ArticlePost>(this.entityManagerFactory.createEntityManager().unwrap(HibernateEntityManager.class).getSession(), post);
List articlePosts = query.where(post.title.contains("jumped")).list() // empty list
But a search with how it is likely stored in Lucene (probable result of SnowballPorter), then it works…
SearchQuery<ArticlePost> query = new SearchQuery<ArticlePost>(this.entityManagerFactory.createEntityManager().unwrap(HibernateEntityManager.class).getSession(), post);
List articlePosts = query.where(post.title.contains("jump")).list() // list of 2
So it seems like when using QueryDSL, that the analyzer isn't being run before it does the query. Can anyone confirm this is the problem, and is there anyway to have them automatically run before QueryDSL runs the query?

Regarding your question, the analyzer is applied per default when using the query DSL. In most cases it makes sense to use the same analyzer for indexing and searching. For this reason the analyzer is applied per default unless 'ignoreAnalyzer' is used.
Why your second example does not work I cannot tell you. SearchQuery is not part of the Hibernate Search or ORM API. It must be an internal class of your application. What's happening in this class? Which type of query is it using?

Related

How can I perform grouping on Solr search results in Spring?

The official Spring documentation provides the following example (slightly simplified here) for grouping results from a Solr query:
Field field = new SimpleField("popularity");
Query query = new SimpleQuery("inStock:true");
SimpleQuery groupQuery = new SimpleQuery(new SimpleStringCriteria("*:*"));
GroupOptions groupOptions = new GroupOptions()
.addGroupByField(field)
.addGroupByQuery(query);
groupQuery.setGroupOptions(groupOptions);
GroupPage<Product> page = solrTemplate.queryForGroupPage("collection-1", query, Product.class);
However, when I try this, it ignores the search conditions (inStock:true) and just performs the grouping on all results ("*:*")
There are several things about this example I had to modify to get it working. First of all, as you may have noticed, the argument query should actually be groupQuery in the queryForGroupPage call. I also removed the Query and put the actual search conditions in groupQuery. The working version is as follows, where groupByField is a SimpleField:
SimpleQuery groupQuery = new SimpleQuery(conditions);
GroupOptions groupOptions = new GroupOptions()
.addGroupByField(groupByField);
groupQuery.setGroupOptions(groupOptions);
return solrTemplate.queryForGroupPage(groupQuery, YourObjectHere.class);

How to retrieve matching element in array in spring mongodb ?

Im trying to retrieve a document with a specific '_id' and a single embedded document with another specific '_id'.
my document is represent a catalog and it contains an array of courses.
example data:
'_id': ObjectId('1111'),
'name': 'example catalog',
...
...
'courses': [
{
'_id': ObjectId('2222'),
'name': 'my course',
...
},
{
....
}
In mongod I run this aggregation query, and get back what I wish for:
db.getCollection('catalogs').aggregate(
{ $match: { '_id': ObjectId('58e8da206ca4f710bab6ef74') } },
{ $unwind: '$courses' },
{ $match: { 'courses._id': ObjectId('58d65541495c851c1703c57f') } })
As I mentioned earlier, I've get back I single catalog instance with a single course instance within.
In my java repo, I was trying to do the same:
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where(Catalog.ID_FIELD).is(catalogId)),
Aggregation.unwind(Catalog.COURSES_FIELD, true),
Aggregation.match(Criteria.where(Catalog.COURSES_FIELD + '.' + Course.ID_FIELD).is(embeddedCourseId))
);
AggregationResults<Catalog> results = mongoTemplate.aggregate(aggregation,
Catalog.class, Catalog.class);
List<Catalog> catalog = results.getMappedResults();
But unfortunately, I've got an instance of my 'example catalog' with empty array of courses.
While debugging, I've found that inside results, there are two props that returns back.
first one is what I've used, called mappedResults (represents the converted object returning from mongoDB) - contains an empty array of courses.
the other one is the rawResults, (represents the data as DBObject) - contains the specific course I query for
my Catalog class contains an ArrayList (if that make any difference).
Please help and let me know what should I do to convert the results properly, or if I did something wrong in my code.
You can try below options. The key is to preserve the structure when mapping the response.
Regular Queries:
Using $positional projection
Query query = new Query();
query.addCriteria(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74")).and("courses.id").is(new ObjectId("58d65541495c851c1703c57f")));
query.fields().include("name").position("courses", 1);
List<Course> courses = mongoTemplate.find(query, Course.class);
Using $elemMatch projection
Query query = new Query();
query.addCriteria(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74")));
query.fields().include("name").elemMatch("courses", Criteria.where("_id").is(new ObjectId("58d65541495c851c1703c57f") ) );
List<Course> Course = mongoTemplate.find(query, Course.class);
Aggregation
Mongo Version >= 3.4 & Spring 1.5.2 Boot / Spring 1.10.1 Mongo.
You can use $addFields stage which will overwrite the courses field with the $filter value while keeping all the existing properties. I couldn't find any addFields builder in current spring version. So I have to use AggregationOperation to create a new one.
AggregationOperation addFields = new AggregationOperation() {
#Override
public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
DBObject dbObject =
new BasicDBObject("courses",
new BasicDBObject("$filter",
new BasicDBObject("input", "$$courses").
append("as", "course").
append("cond",
new BasicDBObject("$eq", Arrays.<Object>asList("$$course._id", new ObjectId("58d65541495c851c1703c57f"))))));
return new BasicDBObject("$addFields", dbObject);
}
};
Aggregation aggregation = Aggregation.newAggregation(
Aggregation.match(Criteria.where("_id").is(new ObjectId("58e8da206ca4f710bab6ef74"))),
addFields
);
Mongo Version = 3.2 & Spring 1.5.2 Boot / Spring 1.10.1 Mongo..
The idea is still same as above but this pipeline uses $project so you'll have to add all the fields that you want to keep in final response. Also used spring helper methods to create the $filter pipeline.
Aggregation aggregation = newAggregation(
Aggregation.match(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74"))),
Aggregation.project("name")
.and(ArrayOperators.Filter.filter("courses").as("course")
.by(ComparisonOperators.Eq.valueOf("course._id").equalToValue(new ObjectId("58d65541495c851c1703c57f")))
).as("courses")
);
Mongo Version <= 2.6
You'll have to use $unwind and add a course field to have spring map it correctly.
The problem that you have here is that your Catalog class has a courses field which maps to a List/ArrayList. But when your aggregation query unwinds the courses array, it is going to output the courses field as a sub-document. The Spring mapper doesn't know how to deal with that because it doesn't match your Catalog object structure.
You haven't fully defined your problem here, but what would probably make more sense is if you had the aggregation return a Course object rather than a Catalog object. In order to do that you're going to need to add a projection stage to your aggregation pipeline so that the result looks exactly like a single Course object. The key is that the data coming back from MongoDB needs to match your object structure.

Hibernate Search Paging with multiple entities

I need to implement a search over multiple entities (which works) and i need to implement pagination over that search (which doesn't work).
So what i do is:
//creates the initial BooleanJunction from the first QueryBuilder
boolJunc = allQueryBuilders.get(0).bool();
for (QueryBuilder buildeForCurrentEntity: allQueryBuilders) {
boolJunc.should(buildeForCurrentEntity.keyword().onFields(searchedFields).matching(searchText).createQuery());
}
//after all BolleanJunctions are set, creates the final lucene query
org.apache.lucene.search.Query mainLuceneQuery = boolJunc.createQuery();
FullTextQuery jpaQuery = fullTextSession.createFullTextQuery(mainLuceneQuery, classes); //classes is array of all classes used to make those QueryBuilder's
//set pagination parameters
jpaQuery.setFirstResult(firstResultIndex);
jpaQuery.setMaxResults(maxNumberOfResults);
List<?> result = jpaQuery.list();
For a search term that finds 10 results:
if i set my paging parameters as firstResultIndex = 0 and maxNumberOfResults = 50, then it works normally
if its firstResultIndex = 0 and maxNumberOfResults = 5 then it dosn't return anything
if its firstResultIndex = 4 and maxNumberOfResults = 50 then it ignores firstResultIndex and just returns all 10 results
Is there something i am missing? How do i make pagination work in my case?

Hibernate search : single search term with space

I am a newbie to hibernate search.
I am trying to implement a engine in which the results are exact matches.
My persistence class changes
#Field(index=Index.UN_TOKENIZED, store=Store.NO)
private String offerTitle;
Implementation changes
String[] offerFields = new String[] { "offerTitle"};
MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_31,offerFields,new KeywordAnalyzer());
org.apache.lucene.search.Query query = parser.parse(queryString);
org.hibernate.Query offerHibQuery = fullTextSession.createFullTextQuery(query, Offer.class);
List<?> offerResults = offerHibQuery.list();
It works fine until the search term has space in it. When search term contains space, it gets no results.
For example: For the search term: "Comcast offer name" I get no results, and the query is broken into offerTitle:Comcast offerTitle:offer offerTitle:name
Is there any way to search for exact match with spaces?
I tried this:
QueryBuilder queryBuilder_1 = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Offer.class).get();
org.apache.lucene.search.Query offerCode_1 = queryBuilder_1.phrase().onField("offerTitle").sentence(queryString).createQuery();
org.hibernate.Query offerCodeHibQuery = fullTextSession.createFullTextQuery(offerCode_1);
List<?> offerCodeResults = offerCodeHibQuery.list();
It is still not working.
You are not tokenizing the field, so you need to search with a single, unanalyzed term. The simplest way to do that, I find, is to go straight to the Lucene APIs and just construct a TermQuery, like:
Query query = new TermQuery(new Term("offerTitle", queryString));
I think this should also work:
queryBuilder_1.keyword().onField("offerTitle").matching(queryString).createQuery();
queryBuilder_1.phrase().withSlop(0)
.onField("offerTitle").sentence(queryString).createQuery()

Search keywords in database

I'm using Eclipselink and have a tricky problem regarding JPA NamedQueries.
My database table contains a column which is from type VARCHAR and stores a comma separated list of keywords as one String.
How can I create a NamedQuery in JPA to search theese keywords?
I'd like to give a list of Strings as a parameter and as a result I'd like to have a list of objects which keyword list contain one of the Strings from the parameter list.
Maybe like the following:
List<String> keywordList = new ArrayList<String>();
keywordList.add("test");
keywordList.add("car");
List<Object> result = em.createNamedQuery("findObjectByKeywords", Object.class)
.setParameter("keywords", keywordList)
.getResultList();
Unfortunately I'm not such a big database/SQL expert. Maybe someone of you can help me?
I hope you understand my problem.
Edit:
I am developing on Weblogic 10.3.6, which means I am not able to use JPA 2.0 features.
Edit2:
I managed to activate JPA 2.0 in my Weblogic Server with the help of Oracle Enterprise Pack for Eclipse. Problem solved, I think.
VALID FOR JPA2.0
As Bhesh commented a simple JPQL won't make it. The resulting SQL has to contain a where clause similar to following:
where keywords like '%keyword1%' or keywords like '%keyword2%' or ... or keywords like '%keywordN%'
This means: We need a loop here!
You could try to build a JPQL by yourself like Bhesh suggested in his first comment, though as he also stated it is not a brilliant idea. But don't worry - JPA provides also a Criteria API which comes handy in such situations. So, although you're not going to have a named query, you can still make it with JPA this way:
public List<YourEntity> findAllByKeywords(List<String> keywords){
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
List<Predicate> predicates = new LinkedList<>();
for (String keyword : keywords) {
predicates.add(builder.like(root.<String>get("keywords"), "%" + keyword + "%"));
}
return entityManager.createQuery(
query.select(root).where(
builder.or(
predicates.toArray(new Predicate[predicates.size()])
)
))
.getResultList();
}
or (always slightly better with Guava)
public List<YourEntity> findAllByKeywords(List<String> keywords){
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
final Root<YourEntity> root = query.from(YourEntity.class);
return entityManager.createQuery(
query.select(root).where(
builder.or(
transform(keywords, toPredicateFunction(builder, root)).toArray(new Predicate[]{})
)
))
.getResultList();
}
private Function<String, Predicate> toPredicateFunction(final CriteriaBuilder builder, final Root<YourEntity> root) {
return new Function<String, Predicate>() {
#Override
public Predicate apply(String input) {
return builder.like(root.<String>get("keywords"), "%" + input + "%");
}
};
}

Categories

Resources