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。