Lucene版本:4.10.2
在使用lucene的时候,不可避免的需要扩展lucene的相关功能来实现业务的需要,比如搜索时,需要在满足一个特定范围内的document进行搜索,如年龄在20和30岁之间的document中搜索并排序。其实lucene自带的NumericRangeQuery类已经能实现这个功能了,如下:
public void testInclusive() throws Exception { Directory dir = FSDirectory(---); IndexSearcher searcher = new IndexSearcher(dir); NumericRangeQuery query = NumericRangeQuery.newIntRange("age",20,30,true,true); TopDocs matches = searcher.search(query,10); }
这个是通过lucene内置的query类型来进行搜索,但是这样使用起来的缺点就是无法再搜索期间生成对应的NumericRangeQuery实例,如比如直接在搜索的时候传入搜索text: age:[20 TO 30],这样就可以动态的使用范围搜索,更为灵活,那么要是实现这个功能,我们就需要自定义QueryParser了。
QueryParser,故名思议,就是讲前台传入的查询条件,解析为Query实例,从而进行查询,所以简单的说,我们需要自定义一个NumericRangeQueryParser,用来实现,从 age:[20 TO 30] 转化为
NumericRangeQuery query = NumericRangeQuery.newIntRange("age",20,30,true,true);
的一个Query实例。
在lucene中扩展QueryParser是比较简单的,我们可以直接继承QueryParser类,然后实现特定的方法就可以了(查询age在20至30岁的记录):
//自定义queryParser class NumericRangeQueryParser extends QueryParser { /** * @param arg0 */ protected NumericRangeQueryParser(String field,Analyzer analyzer) { //调用父类的构造方法 super(field,analyzer); // TODO Auto-generated constructor stub } //获取query实例的方法 public Query getRangeQuery(String field,String part1,String part2,boolean startInclusive,boolean endInclusive) throws ParseException { //调用父类的getRangeQuery方法获取query实例 TermRangeQuery query = (TermRangeQuery) super.getRangeQuery方法获取query实例(field, part1, part2, startInclusive,endInclusive); //如果是搜索age Field if("age".equals(field)) { System.out.println(query.getLowerTerm().utf8ToString()); System.out.println(query.getUpperTerm().utf8ToString()); //按照业务需求处理query生成新的query实例并返回 return NumericRangeQuery.newDoubleRange("price", Double.parseDouble(query.getLowerTerm().utf8ToString()), Double.parseDouble(query.getUpperTerm().utf8ToString()), query.includesLower(), query.includesUpper()); } else { return query; } } }
其实代码很简单,因为我只是将范围查询的上下限从Int类型格式化成Double类型。其中最重要的一条代码是:
NumericRangeQuery.newDoubleRange("price", Double.parseDouble(query.getLowerTerm().utf8ToString()), Double.parseDouble(query.getUpperTerm().utf8ToString()), query.includesLower(), query.includesUpper());
NumericRangeQuery.newDoubleRange是调用了lucene自带的方法来生成相应的query实例,在这里是浮点数范围查询Query实例。其中参数的解释:
query.getLowerTerm().utf8ToString():getLowerTerm是指获取范围查询中的下限,在这里是20。getLowerTerm()返回一个BytesRef对象,lucene自定义的一种对象,我们需要转换成String类型再转成Double类型的对象,传给NumericRangeQuery.newDoubleRange方法,在这里我一开始想到的是toString方法,结果老是报错,最后查看源码才知道应该使用utf8ToString()方法,它会将BytesRef转成utf8编码的String对象。
public String utf8ToString() Interprets stored bytes as UTF8 bytes, returning the resulting string
query.getUpperTerm().utf8ToString():与query.getLowerTerm().utf8ToString()同理,获取范围查询中的上限,在这里是30
query.includesLower()和query.includesUpper():是否包含临界值,在这里值是否包含年龄为20和30岁的记录,即[20,30]或[20,30)…
下面我没来测试NumericRangeQueryParser:
public static void main(String[] args) throws IOException, ParseException { String expression = "age:[10 TO 20]"; Analyzer analyzer = new StandardAnalyzer(); QueryParser parser = new QueryParser("age",analyzer); Query query = parser.parse(expression); QueryParser testParser = kscSearch.new NumericRangeQueryParser("age",analyzer); Query testQuery = testParser.parse(expression); System.out.println(expression + " parser to " + query); System.out.println(expression + " parser to " + testQuery); }
测试结果如下:
age:[10 TO 20] parser to age:[10 TO 20] age:[10 TO 20] parser to price:[10.0 TO 20.0]
可以看到使用NumericRangeQueryParser后,10和20都变成了浮点数。
相应的我们可以自定义日期QueryParser或者是更为复杂的QueryParser。不过从这个简单的NumericRangeQueryParser中我们可以看到QueryParser的原理,就是讲给定的查询字符串解析生成Lucene能识别的Query实例,从而进行查询,我们这里是使用了Lucene内置的NumericRangeQuery扩展QueryParser,如果有需要,甚至可以自定义NumericRangeQuery。