使用Lucene解析搜索查询并基于此构建Hibernate标准

时间:2015-01-21 05:20:01

标签: java hibernate lucene hibernate-search

要求是在有限数量的字段中构建简化的搜索功能,这些字段保存在单独的单个表中。目前使用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。

1 个答案:

答案 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)
            );
}

希望有人会觉得这很有用。