## 목차 ##
## 1. 간단한 검색 기능 구현 ##
(1) Term 검색
- 텀을 포함하는 문서를 검색하는 가장 단순한 방법
TermQuery
객체를IndexSearcher
로 넘겨준다.
IndexSearcher searcher = new IndexSearcher(indexDir);
//필드 문자열이 "str"인 텀 객체 생성
Term term = new Term("fieldName", "str");
//Term 객체를 TermQuery 객체로 변환
Query query = new TermQuery(term);
//TermQuery 객체로 검색
TopDocs hits = searcher.search(query, 10);
(2) QueryParser로 사용자가 입력한 검색어 파싱
QueryParser
클래스- 사용자가 입력한 텍스트 질의를 동일한 내용의 Query 객체로 변환
IndexSearcher
는Query
객체를 통해 검색할 수 있다.
IndexSearcher searcher = new IndexSearcher(indexDir);
//QueryParser 객체 생성
QueryParser parser = new QueryParser(Version.LUCENE_30, "contents", new SimpleAnalyzer());
String q = "mock OR junit"); //텍스트 검색어
Query query = parser.parse(q); //파싱하여 Query객체 생성
//Query 객체로 검색
TopDocs hits = searcher.search(query, 10);
QueryParser
객체는 분석기를 지정해주어야한다.- 생성자에서 분석기 객체를 매개변수로 넘겨준다.
- 색인에 추가된 텀과 동일한 텀으로 Query를 만들어야한다.
- 따라서 색인 과정에서 사용한 분석기와 동일한 분석기를 사용하는것이 좋다.
- 분석기는 사용자가 입력한 검색어를 Term으로 분리한다.
- QueryParser가 다룰 수 있는 표현식의 종류
## 2. IndexSearcher 활용 ##
(1) IndexSearcher 인스턴스 생성
- 색인 Directory 필요
Directory indexDir = FSDirectory.open(new File(indexPath));
IndexReader reader = IndexReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);
//IndexSearcher searcher = new IndexSearcher(indexDir);
IndexSearcher
객체 생성시 넘겨주어야 하는 매개변수- 인덱스
Directory
혹은IndexReader
- 인덱스
IndexReader
- 색인 내용 불러오기
IndexSearcher
가 검색할 수 있게 기본적인 색인 관련 API 제공IndexSearcher
는IndexReader
의 API를 활용해 검색 메소드를 제공하는 껍데기일 뿐이다.IndexReader.reopen()
은 필요 자원을 최소화하여 색인을 새로 열어 최신 내용을 반영한다.
(2) TopDocs 결과 활용
IndexSearcher
의 주요search
메소드TopDocs search(Query query, int n)
- 가장 기본적인 검색 메소드로 n개의 문서만 가져온다.
TopDocs search(Query query, Filter filter, int n)
- 필터에 해당하는 n개의 문서만 가져온다.
TopFieldDocs search(Query query, Filter filter, int n, Sort sort)
- 필터에 해당하는 n개의 문서를 sort로 정렬한다.
void search(Query query, Collector results)
- 다른 기준으로 첫 번째 페이지를 확보하고자 하는 경우에 사용
void search(Query query, Filter filter, Collector results)
- 위 메소드와 동일하지만 필터에 해당하는 문서 집합만 대상으로 한다.
TopDocs
의 멤버변수 혹은 메소드- totalHits
- 검색 결과의 건수
- scoreDocs
- 결과를 담고있는 ScoreDoc 객체 배열
- getMaxScore()
- 가장 높은 점수 리턴
- totalHits
ScoreDoc
- 실제 문서를 참조하는 객체
ScoreDoc.doc
으로 문서 ID 참조IndexSearcher.doc(ScoreDoc.doc)
을 통해 실제 문서 반환
(3) 준실시간 검색
IndexReader
를 다시 생성하지 않고도 새로운 추가 내용 확인 가능IndexWriter.getReader()
- 버퍼에 쌓여있는 변경 사항을 Directory에 반영하고 이를 포함하는
IndexReader
를 반환 - 준실시간 검색의 핵심 메소드
- 버퍼에 쌓여있는 변경 사항을 Directory에 반영하고 이를 포함하는
reader.reopen()
- 내부적으로는
getReader()
를 다시 호출할 뿐이다. - 변경사항이 있으면 이를 포함하는 새로운
IndexReader
객체 생성 - 변경사항이 없는 색인 파일은 그대로 물려받아 효율적이다.
- 내부적으로는
## 3. 연관도 점수 ##
(1) 점수 계산
tf × idf^2 × boost × lengthNorm × coord × queryNorm
tf
- term frequency
- 문서에 term이 나타나는 빈도수
idf
- inverse document frequency
- term이 얼마나 독보적인지 나타내는 지표
- term을 포함하는 전체 문서 수가 적을수록 높은 점수를 가진다.
lengthNorm
- 필드에 속한 텀의 개수를 정규화한 값
- 필드 내용의 길이가 짧을수록 높은 점수를 가진다.
coord
- 질의에 지정된 텀을 더 많이 포함하는지를 나타내는 지표
- ex) query가 'apple banana'이면 'apple'만 포함한 문서보다 'apple'과 'banana' 모두 포함한 문서가 더 높은 점수를 갖는다.
queryNorm
- 질의에 있는 각 텀의 norm의 제곱 합을 정규화한 값
Similarity
클래스의 하위 클래스 구현으로 유사도 점수 계산 공식 변경 가능- 루씬은 기본적으로
DefaultSimilarity
클래스 사용
- 루씬은 기본적으로
Explanation
객체를 활용해 유사도 점수 산출 내역 확인IndexSearcher.explain()
메소드가 리턴
Explanation explanation = searcher.explain(Query, ScoreDoc.doc);
System.out.println(explanation.toString());
## 4. 다양한 종류의 질의 ##
(1) TermQuery
- Term 검색
//field 내용에 text를 포함하는 문서 검색을 위한 Term 객체 생성
Term term = new Term("field", "text");
Query query = new TermQuery(term);
(2) TermRangeQuery
- Term 범위 검색
//title의 첫 글자가 a~c로 시작하는 문서 검색을 위한 TermRangeQuery 생성
Query query = new TermRangeQuery("title", "a", "c", "true", "true");
매개변수 마지막 두 개의 boolean 값은 각각 시작점과 종료점의 범위를 포함할 것인지를 설정한다. 즉, "a"와 "c"를 사용할 것인지 아닌지 결정하는 것이다.
(3) NumericRangeQuery
- 숫자 범위 검색
- NumericField를 검색할 때 사용
NumericRangeQuery query = NumericRangeQuery.newIntRange("pubmonth", 200605, 200609, true, true);
(4) PrefixQuery
- 접두어 검색
- 지정한 문자열로 시작하는 텀을 갖는 문서 검색
// /home/computers 하위 디렉토리의 모든 디렉토리 검색
Term term = new Term("path", "/home/computers");
PrefixQuery query = new PrefixQuery(term);
(5) BooleanQuery
- 논리 구조를 사용해 다양한 질의를 묶어 복잡한 검색이 가능하다.
- AND, OR, NOT
//제목이 computer인 문서
Query searchingTitleQuery = new TermQuery("title", "computer");
//출간일이 2006년 5월 ~ 2006년 9월인 문서
Query pubmonthQuery = NumericRangeQuery.newIntRange("pubmonth", 200605, 200609, true, true);
//위에서 지정한 두 개의 쿼리를 모두 만족하는 문서 검색
BooleanQuery query = new BooleanQuery();
query.add(searchingTitleQuery, BooleanClause.Occur.MUST);
query.add(pubmonthQuery, BooleanClause.Occur.MUST);
searcher.search(query, 5);
BooleanClause
Occur.MUST
- Query에 해당하는 문서만 반환
Occur.SHOULD
- Query에 해당하는 문서를 반환
- 해당하지 않더라도 다른 조건에 해당하면 반환
- SHOULD를 사용하면 OR 조건 검색을 할 수 있다.
Occur.MUST_NOT
- Query에 해당하지 않는 문서만 반환
(6) PhraseQuery
구문을 포함하는 문서 검색
- slop값에 따라 정확히 일치하지 않아도 된다.
slop
- 문서의 구문과 일치하기 위해 구문의 텀을 이동해야하는 횟수
private void index() {
...
Document doc = new Document();
Field field = new Field("contents", "the quick brown fox jumped over the lazy dog",
Field.Store.YES,
Field.Index.ANALYZED);
doc.add(field);
writer.addDocument(doc);
writer.close();
}
private void search() {
...
PhraseQuery query = new PhraseQuery();
query.setSlop(2);
//텀 2개를 순서대로 구문에 추가
query.add(new Term("contents", "fox"));
query.add(new Term("contents", "quick"));
searcher.search(query, 10);
}
"fox quick"은 fox를 오른쪽으로 세 번 옮겨야 "quick (brown) fox"가 되기때문에 slop 2로는 검색이 되지 않는다.
- PhraseQuery 점수 계산
- 구문 질의 결과 문서 점수는 (1/이동거리)에 따라 계산한다.
(7) WildcardQuery
- 단어를 완벽하게 모르는 상태에서도 검색 가능
- ? : 글자가 없거나 한 글자
- ex) ?ather : ather 앞에 글자가 없거나 한 글자인 단어
- * : 글자가 없거나 한 글자 혹은 여러 글자
- ex) ?ather : ather 앞에 글자가 없거나 있거나. 즉, ather로 끝나는 단어
- ? : 글자가 없거나 한 글자
// ild앞에 글자가 없거나 한 글자, 그리고 뒤에 글자가 없거나 하나 이상의 글자인 단어로 쿼리 생성
Term term = new Term("contents", "?ild*");
Query query = new WildcardQuery(term);
(8) FuzzyQuery
- 비슷한 단어 검색
- Levenshtein 편집거리 기반
private void index() {
...
doc.add(new Field("contents", "fuzzy", Field.Store.YES, Field.Index.ANALYZED));
writer.addDocument(doc);
...
doc.add(new Field("contents", "wuzzy", Field.Store.YES, Field.Index.ANALYZED));
writer.add(Document(doc);
}
private void search() {
...
Query query = new FuzzyQuery(new Term("contents", "wuzza"));
searcher.search(query, 10);
}
wuzzy가 편집거리 1이므로 먼저 나올 것이다.
유사도 임계값 보다 작은 문서만 리턴
- threshold = 편집거리 / 문자열 길이
FuzzyQuery
는 색인된 모든 텀을 대상으로 편집거리를 계산- 느리다.
(9) MatchAllDocsQuery
모든 문서 반환
Query query = new MatchAllDocsQuery(Field);
- 해당 필드에 지정했던 중요도 값을 유사도 점수로 활용하여 정렬
## QueryParser로 질의 표현식 파싱 ##
- QueryParser를 사용하면 간편하게 Query를 생성할 수 있다.
(1) Query.toString()
QueryParser
로 파싱한 질의식 확인
(2) TermQuery
QueryParser
는 검색어 표현식의 단일 단어를 TermQuery로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
Query query = parser.parse("computers");
System.out.println(query);
출력결과
subject:computers
(3) 텀 범위 검색
- 문자열, 날짜 등의 범위 검색
- 괄호 사용
- 시작 텀과 종료 텀 사이 TO 사용
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//title필드 내용이 a~c로 시작하는 문서를 검색하기 위한 쿼리
Query query = parser.parse("title:[a TO c]");
(4) 숫자, 날짜 범위 검색
QueryParser
는NumericRangeQuery
를 지원하지 않는다.- 루씬은 필드별로
NumericField
로 색인했는지 여부를 저장하지 않기 때문
- 루씬은 필드별로
(5) 접두어, 와일드카드 질의
텀에 별표(*)나 물음표(?)가 포함돼 있으면
QueryParser
가WildcardQuery
로 변환된다.별표 기호가 단어 뒤에만 있으면
PrefixQuery
로 변환된다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//QueryParser가 PrefixQuery로 변환
Query query = parser.parse("head*");
//QueryParser가 WildcardQuery로 변환
Query query = parser.parse("?ello*");
(6) BooleanQuery
Term이 여러개인 경우
BooleanQuery
로 변환- Boolean 연산자의 기본값은 OR 연산이다.
AND, OR, NOT 연산자를 사용하면
QueryParser
가BooleanQuery
로 변환
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//기본 연산자를 AND 연산으로 지정
parser.setDefaultOperator(QueryParser.AND_OPERATOR);
//"apple"과 "banana" 모두 들어있는 문서 검색을 위한 쿼리
Query query = parser.parse("apple banana");
//Query query = parser.parse("+apple +banana");
//"apple"은 포함하고 "banana"는 포함하지 않는 문서 검색을 위한 쿼리
Query query2 = parser.parse("apple AND NOT banana");
//Query query2 = parser.parse("+apple -banana");
(7) Phrase Query
- 여러 Term이 큰따옴표 안에 있으면
PhraseQuery
로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//slop값을 5로 지정
parser.setPhraseSlop(5);
//"linux kafka apache" 구문의 slop 5 이내 구문이 포함된 문서 검색을 위한 쿼리
Query query = parser.parse("\"linux kafka apache\"");
구문 검색으로 넘기기 위해 큰따옴표(")를 escape 했다.
(8) Fuzzy Query
- 물결기호(~)를 붙이면
QueryParser
가FuzzyQuery
로 변환한다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
Query query = parser.parse("rinux~");
//Query query = parser.parse("rinux~0.77"); //FuzzyQuery의 유사도 임계값 0.7로 설정
(9) MatchAllDocsQuery
- 질의 표현식으로 *:*을 입력하면 MatchAllDocsQuery 생성
(10) 필드 선택
QueryParser
에서 지정한 필드는 기본값일 뿐 쿼리 필드 이름은 제한이 없다.
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//title 필드값이 lucene이고 author 필드값이 kelly인 문서 검색을 위한 쿼리
Query query = parser.parse("(title:lucene AND author:kelly");
(11) Term 중요도 지정
- ^ 연산자 사용
QueryParser parser = new QueryParser(Version.LUCENE_30, "subject", analyzer);
//"apple"은 중요도를 2.0으로 지정, "banana"는 중요도 기본 값 사용
Query query = parser.parse("apple^2.0 banana");
(12) QueryParser를 사용해야 하는가?
- 의도한 Query가 나오지 않아 원하는 검색결과를 얻지 못하는 경우를 조심
- 대부분이 분석기를 통과하면서 검색어가 변경되는 현상과 관련있다.
'프레임워크 > 루씬(Lucene)' 카테고리의 다른 글
(루씬) 4 - 고급 검색 기법 (0) | 2020.05.16 |
---|---|
(루씬) 2 - 색인(Indexing) (0) | 2020.05.16 |
(루씬) 1 - 루씬과의 만남 (0) | 2020.05.16 |