要求是在有限数量的字段中构建简化的搜索功能,这些字段保存在单独的单个表中。目前使用Solr或类似物不是一种选择,一切都必须在一个webapp中工作。该数据库是MSSQL。我想要做的是利用Lucene查询解析器并从中构建Hibernate标准。尽管我最初的印象是它不会太难,但我无法弄清楚如何为复杂查询构建标准。
这是我用Lucene(4.7.2)
解析查询字符串的快速测试Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);
QueryParser luceneParser = new QueryParser(Version.LUCENE_47, "", analyzer);
String queryString = "(name:\"Luke Skywalker\" AND father:unknown OR fname:Luke) or (name:yoda)";
Query luceneQuery = luceneParser.parse(queryString);
...
public class QueryInterpreter {
public void parse(Query query) {
if (query instanceof TermQuery) {
termQuery((TermQuery) query);
} else if (query instanceof BooleanQuery) {
booleanQuery((BooleanQuery) query);
} else if (query instanceof PhraseQuery) {
phraseQuery((PhraseQuery) query);
} else {
throw new IllegalArgumentException("");
}
}
public void booleanQuery(BooleanQuery query) {
for (BooleanClause clause : query.getClauses()) {
parse(clause.getQuery());
}
}
public void phraseQuery(PhraseQuery query) {
StringBuilder sb = new StringBuilder();
for (Term term : query.getTerms()) {
sb.append(term.text());
sb.append(" ");
}
}
public void termQuery(TermQuery query) {
Term term = query.getTerm();
}
}
Lucene首先将搜索字符串转换为(+name:\"Luke Skywalker\" +father:unknown fname:Luke) name:yoda
。基本上它会迭代通过为每个术语设置isRequired()的术语。 Hibernate的工作方式不同 - 您创建一个条件对象并继续添加带有值对的Criterions。我无法弄清楚如何将一个转换为另一个。我认为我需要的是一个通用的Junction对象来附加Criterions。
答案 0 :(得分:1)
最后想出来,将在这里分享我的解决方案,万一有人将面临同样的问题。
正确方向的第一步是认识到QueryParser
不能很好地处理布尔逻辑。例如,(+name:\"Luke Skywalker\" +father:unknown fname:Luke) name:yoda
是与(name:\"Luke Skywalker\" AND father:unknown OR fname:Luke) or (name:yoda)
不同的搜索。不知道为什么QueryParser甚至接受布尔逻辑,这只是简单的混淆。
解决方案是使用PrecedenceQueryParser
。
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);
PrecedenceQueryParser luceneParser = new PrecedenceQueryParser(analyzer);
luceneParser.setAllowLeadingWildcard(true);
Query luceneQuery = luceneParser.parse(searchQuery, "name");
然后从中创建一个Hibernate Criterion。显然,你不能在关系数据库中支持全范围的Lucene搜索功能,但这从来就不是必需的。
public Criterion buildHibernateQuery(Query luceneQuery) {
return parse(luceneQuery);
}
private Criterion parse(Query query) {
if (query instanceof TermQuery) {
return parse((TermQuery) query);
} else if (query instanceof BooleanQuery) {
return parse((BooleanQuery) query);
} else if (query instanceof PhraseQuery) {
return parse((PhraseQuery) query);
} else if (query instanceof PrefixQuery) {
return parse((PrefixQuery) query);
} else if (query instanceof WildcardQuery) {
return parse((WildcardQuery) query);
} else {
LOG.error(String.format("%s unsupported", query.getClass()));
}
}
private Criterion parse(TermQuery query) {
Term term = query.getTerm();
return createNameValueRestriction(term.field(), term.text());
}
private Criterion parse(BooleanQuery query) {
if (query.getClauses().length == 1) {
return parse(query.getClauses()[0].getQuery());
}
Junction junction = createJunction(query.getClauses()[0]);
for (BooleanClause clause: query.getClauses()) {
junction.add(parse(clause.getQuery()));
}
return junction;
}
private Junction createJunction(BooleanClause booleanClause) {
if (booleanClause.isRequired()) {
return Restrictions.conjunction();
} else {
return Restrictions.disjunction();
}
}
private Criterion parse(PhraseQuery query) {
String field = query.getTerms()[0].field();
StringBuilder phraseBuilder = new StringBuilder();
for (Term term : query.getTerms()) {
phraseBuilder.append(term.text());
phraseBuilder.append(" ");
}
return createNameValueRestriction(field, phraseBuilder.toString().trim());
}
private Criterion createNameValueRestriction(String field, String value) {
return Restrictions.and(
Restrictions.eq("jsonPath", field),
Restrictions.eq("answer", value)
);
}
private Criterion parse(PrefixQuery query) {
Term term = query.getPrefix();
return parseLikeQuery(term.field(), term.text(), MatchMode.START);
}
private Criterion parse(WildcardQuery query) {
Term term = query.getTerm();
String wildCardEscaped = Pattern.quote(String.valueOf(WildcardQuery.WILDCARD_STRING));
String termText = term.text().replaceAll(wildCardEscaped, "");
return parseLikeQuery(term.field(), termText, MatchMode.ANYWHERE);
}
private Criterion parseLikeQuery(String field, String value, MatchMode matchMode) {
return Restrictions.and(
Restrictions.eq("jsonPath", field),
Restrictions.like("answer", value, matchMode)
);
}
希望有人会觉得这很有用。