Reputation: 30957
I have a full-text search working with hibernate search and lucene, in that I can successfully search for a given model entity across specific fields.
However, rather than search for one type of entity at a time, I want to implement a 'universal' search where different entity types are searched for simultaneously, matching the search phrase to the appropriate fields on each different entity type, and then have the results ranked by relevance to the search terms, irrespective of the entity type.
So for example let's say I have different entities, Foo and Bar
@Entity
@Indexed
@AnalyzerDef(
name="fulltext",
tokenizer=@TokenizerDef(factory=StandardTokenizerFactory.class),
filters={
@TokenFilterDef(factory=LowerCaseFilterFactory.class),
@TokenFilterDef(factory=SnowballPorterFilterFactory.class,
params={@Parameter(name="language", value="English") })
}
)
public class Foo {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer fooId;
@Column
@Field
@Analyzer(definition="fulltext")
private String fieldA;
...
@Entity
@Indexed
public class Bar {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer barId;
@Column
@Field
@Analyzer(definition="fulltext")
private String fieldB;
@Column
@Field
@Analyzer(definition="fulltext")
private String fieldC;
...
So I want to search for "some text" and match over Foo.fieldA, and Bar.fieldB and/or Bar.fieldC
The current type of search I have working is specific to a particular entity, for example:
fullTextSession = Search.getFullTextSession(hibernateSession);
Query query = fullTextSession.createFullTextQuery(
fullTextSession
.getSearchFactory()
.buildQueryBuilder()
.forEntity(Foo.class)
.get()
.keyword()
.onFields("fieldA")
.matching("some text")
.createQuery(),
Foo.class);
query.list() // gets ranked list of Foo entities matching "some text"
Clearly, the above Lucene query is specific to the Foo entity, and even the Foo.fieldA
So, is it possible to amend the Lucene query to also include Bar results, matching on the Bar.fieldB and Bar.fieldC fields?
I know the fullTextSession.createFullTextQuery(fulltextSession, Class...)
method will also accept Bar.class, to return Bar results, but I don't know how to modify the actual query to search over Bar entities in the first place.
Another way I was thinking of solving this is to do separate queries, one for Foo entities, one for Bar entities, then merge the two result sets and sort them by 'match relevance score' - but I can't find how to get this score for results, either!
EDIT the above approach will likely not work - turns out you can get the score of the results via projections but the docs state that scores from separate queries can't be meaningfully compared:
FullTextQuery.SCORE: returns the document score in the query. Scores are handy to compare one result against an other for a given query but are useless when comparing the result of different queries.
Apologies if I'm covering well-trodden ground here but I've been searching, clearly in the wrong places, for hours on this and can't find anything helpful in the documentation, which is frustrating as I imagine this is a fairly common use-case for Lucene.
Upvotes: 2
Views: 891
Reputation: 30957
Inspired by Hardy's answer, I used a BooleanQuery with two clauses, both with an Occur.SHOULD on them which effectively acts as an OR. This results in the desired behaviour for the query.
here's the code:
...
fullTextSession = Search.getFullTextSession(hibernateSession);
String searchPhrase = "some text";
org.apache.lucene.search.Query fooQuery =
fullTextSession
.getSearchFactory()
.buildQueryBuilder()
.forEntity(Foo.class)
.get()
.keyword()
.onFields("fieldA")
.matching(searchPhrase)
.createQuery();
org.apache.lucene.search.Query barQuery =
fullTextSession
.getSearchFactory()
.buildQueryBuilder()
.forEntity(Bar.class)
.get()
.keyword()
.onFields("fieldB", "fieldC")
.matching(searchPhrase)
.createQuery();
BooleanQuery query = new BooleanQuery();
query.add(new BooleanClause(fooQuery, BooleanClause.Occur.SHOULD));
query.add(new BooleanClause(barQuery, BooleanClause.Occur.SHOULD));
Query hibernateQuery =
fullTextSession.createFullTextQuery(query, Foo.class, Bar.class);
...
Upvotes: 0
Reputation: 19109
You can write two queries and combine them via a BooleanQuery
using Occur.SHOULD
. Then use createFullTextQuery(booleanQuery, Foo.class, Bar.class);
to search for both type of entities.
Upvotes: 3