앞선 포스트에서 IndexWriter를 사용한 색인을 알아보았습니다.
이번에는 IndexSearcher를 사용한 Document 검색과 검색을 할 때 사용되는
Query에 대해서 알아보고 더불어..Filter까지도 함께 알아보도록 하겠습니다.
우선 간단한 IndexSearcher의 테스트 케이스입니다.
IndexSearcherTest.java우선 간단한 IndexSearcher의 테스트 케이스입니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.tistory.devyongsik.search; | |
import java.io.IOException; | |
public class IndexSearcherTest { | |
private String[] ids = {"1","2","3"}; | |
private String[] titles = {"lucene in action action","hadoop in action","ibatis in action"}; | |
private String[] contents = {"lucene is a search engine", "hadoop is a dist file system","ibatis is a db mapping tool"}; | |
private int[] prices = {4000, 5000, 2000}; | |
private Directory directory = new RAMDirectory(); | |
private IndexWriter getWriter() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_33, new WhitespaceAnalyzer(Version.LUCENE_33)); | |
IndexWriter indexWriter = new IndexWriter(directory, conf); | |
return indexWriter; | |
} | |
@Before | |
public void init() throws CorruptIndexException, LockObtainFailedException, IOException { | |
IndexWriter indexWriter = getWriter(); | |
for(int i = 0; i < ids.length; i++) { | |
Document doc = new Document(); | |
doc.add(new Field("ids", ids[i], Field.Store.YES, Field.Index.NOT_ANALYZED)); | |
doc.add(new Field("titles", titles[i], Field.Store.YES, Field.Index.ANALYZED)); | |
doc.add(new Field("titles2", titles[i], Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS)); | |
doc.add(new Field("contents", contents[i], Field.Store.YES, Field.Index.ANALYZED, TermVector.YES)); | |
NumericField numField = new NumericField("price", Field.Store.YES, true); | |
numField.setIntValue(prices[i]); | |
doc.add(numField); | |
indexWriter.addDocument(doc); | |
} | |
//indexWriter.commit(); | |
indexWriter.close(); | |
} | |
@Test | |
public void searchByTerm() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
Term t = new Term("ids", "1"); | |
Query q = new TermQuery(t); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(1, docs.totalHits); | |
t = new Term("titles", "action"); | |
q = new TermQuery(t); | |
docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(3, docs.totalHits); | |
ScoreDoc[] hits = docs.scoreDocs; | |
for(int i = 0; i < hits.length; i++) { | |
System.out.println(hits[i].doc); | |
System.out.println(hits[i].score); | |
Document resultDoc = indexSearcher.doc(hits[i].doc); | |
System.out.println(resultDoc.get("titles")); | |
} | |
} | |
@Test | |
public void searchByBooleanQuery() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
BooleanQuery resultQuery = new BooleanQuery(); | |
Term t = new Term("ids", "1"); | |
Query q = new TermQuery(t); | |
resultQuery.add(q, Occur.SHOULD); | |
Term t2 = new Term("contents", "ibatis"); | |
Query q2 = new TermQuery(t2); | |
resultQuery.add(q2, Occur.SHOULD); | |
TopDocs docs = indexSearcher.search(resultQuery, 10); | |
Assert.assertEquals(2, docs.totalHits); | |
} | |
@Test | |
public void searchByTermRangeQuery() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
Query q = new TermRangeQuery("titles2", "h", "j", true, true); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(2, docs.totalHits); | |
} | |
@Test | |
public void searchByNumericRangeQuery() throws CorruptIndexException, IOException { | |
IndexSearcher indexSearcher = new IndexSearcher(directory); | |
Query q = NumericRangeQuery.newIntRange("price", 2000, 4000, true, true); | |
TopDocs docs = indexSearcher.search(q, 10); | |
Assert.assertEquals(2, docs.totalHits); | |
} | |
} |
특별히 어려운 코드는 없을 것 입니다.
테스트 케이스가 4가지가 있는데요 나중에 이야기 할
Query 종류에 따른 사용을 보여드리기 위해서 각각 테스트 케이스를 작성하였습니다.
이렇게 받아온 TopDocs 클래스로부터 ScoreDoc[] 을 얻어냅니다.
IndexSearcher를 사용 할 때의 주의 할 점은 여러가지가 있겠지만
그 중 가장 중요한 것이 IndexSearcher의 생성은 비용이 많이 들어가는 부분이기 때문에
가급적 단일 인스턴스를 여러 스레드가 사용 하는 형식으로 구현을 하는 것이 좋고
IndexWriter에 의한 변경 내용을 바로 IndexSearcher가 확인 할 수 없기 때문에 이에 대한 처리를
고민해봐야 합니다. (이 부분은 나중에.. 다시 언급 할 예정입니다.)
IndexSearcher는 기본적으로 Query 클래스와 검색을 해 올 Document의 숫자를
지정하도록 되어있습니다.
TopDocs docs = indexSearcher.search(Query, 10);
Query는 나중에 따로 이야기를 할 예정이고 일반적으로 최초 검색 결과 페이지에서
보여 줄 정도의 개수만을 검색하고 이후 페이징에 따라서 검색 해올 개수를 늘려가는
방식으로 구현합니다. 이렇게 받아온 TopDocs 클래스로부터 ScoreDoc[] 을 얻어냅니다.
ScoreDoc[] hits = docs.scoreDocs;
//이 ScoreDoc은 실제 검색 된 Document의 내부 ID와 점수를 가지고 있습니다.
for(int i = 0; i < hits.length; i++) {
System.out.println(hits[i].doc);
System.out.println(hits[i].score);
}
위 주석에도 작성하였지만 이 ScoreDoc은 자기 자신의 docID와 (색인 파일내에서 유일한 ID 값입니다.) 점수를 가지고 있습니다.
그리고 검색 된 내용을 가지고 오고 싶을 때는 아래와 같이 구현합니다.
그리고 검색 된 내용을 가지고 오고 싶을 때는 아래와 같이 구현합니다.
Document resultDoc = indexSearcher.doc(hits[i].doc);
System.out.println(resultDoc.get("titles"));
실제 Document로부터 색인시 필드의 옵션 중 Store 옵션을 YES 설정 한 필드에 대해서는
String으로 된 필드의 내용을 얻어 올 수 있습니다.
앞에서 잠깐 언급하였지만 IndexSearcher는 IndexWriter에 의한 변경 내용을 자동으로 반영하여 보여주지 못 합니다.
이 부분에 대한 처리를 같이 살펴보고.. 2.9부터 지원이 되는걸로 생각이 되는 Near Real Time Search에 대해서
살펴보고 그 후에 쿼리들에 대한 이야기를 해보려고 합니다.
예제 소스들은https://github.com/need4spd/aboutLucene
에서 체크아웃 받으 실 수 있습니다. 살펴보고 그 후에 쿼리들에 대한 이야기를 해보려고 합니다.
예제 소스들은https://github.com/need4spd/aboutLucene