1.Lucene簡介
Lucene是apache下的一個(gè)開源的全文檢索引擎工具包。
1.1.全文檢索(Full-text Search)
1.1.1.定義
全文檢索就是先分詞創(chuàng)建索引踩验,再執(zhí)行搜索的過程宫莱。
分詞:就是將一段文字分成一個(gè)個(gè)單詞
全文檢索就將一段文字分成一個(gè)個(gè)單詞去查詢數(shù)據(jù)!!纽什!
1.1.2.應(yīng)用場(chǎng)景
1.1.2.1.搜索引擎(了解)
搜索引擎是一個(gè)基于全文檢索纪岁、能獨(dú)立運(yùn)行、提供搜索服務(wù)的軟件系統(tǒng)庇谆。
1.1.2.2.電商站內(nèi)搜索(重點(diǎn))
思考:電商網(wǎng)站內(nèi),我們都是通過輸入關(guān)鍵詞來搜索商品的署辉。如果我們根據(jù)關(guān)鍵詞族铆,直接查詢數(shù)據(jù)庫,會(huì)有什么后果哭尝?
答:我們只能使用模糊搜索哥攘,來進(jìn)行匹配,會(huì)導(dǎo)致很多數(shù)據(jù)匹配不到材鹦。所以逝淹,我們必須使用全文檢索。
1.2.Lucene實(shí)現(xiàn)全文檢索的流程
[圖片上傳失敗...(image-a40a64-1563276766589)]
全文檢索的流程分為兩大部分:索引流程桶唐、搜索流程栅葡。
索引流程:采集數(shù)據(jù)--->構(gòu)建文檔對(duì)象--->創(chuàng)建索引(將文檔寫入索引庫)。
搜索流程:創(chuàng)建查詢--->執(zhí)行搜索--->渲染搜索結(jié)果尤泽。
2.入門示例
2.1.需求
使用Lucene實(shí)現(xiàn)電商項(xiàng)目中圖書類商品的索引和搜索功能欣簇。
2.2.配置步驟說明
(1)搭建環(huán)境(先下載Lucene)
(2)創(chuàng)建索引庫
(3)搜索索引庫
2.3.配置步驟
2.3.1.第一部分:搭建環(huán)境(創(chuàng)建項(xiàng)目,導(dǎo)入包)
前提:已經(jīng)創(chuàng)建好了數(shù)據(jù)庫(直接導(dǎo)入book.sql文件)
2.3.1.1.第一步:下載Lucene
Lucene是開發(fā)全文檢索功能的工具包坯约,使用時(shí)從官方網(wǎng)站下載熊咽,并解壓。
官方網(wǎng)站:http://lucene.apache.org/
下載地址:http://archive.apache.org/dist/lucene/java/
下載版本:4.10.3(要求:jdk1.7及以上)
核心包lucene-core-4.10.3.jar(附常用API)
2.3.1.2.第二步:創(chuàng)建項(xiàng)目闹丐,導(dǎo)入包
mysql5.1驅(qū)動(dòng)包:mysql-connector-java-5.1.7-bin.jar
核心包:lucene-core-4.10.3.jar
分析器通用包:lucene-analyzers-common-4.10.3.jar
查詢解析器包:lucene-queryparser-4.10.3.jar
項(xiàng)目結(jié)構(gòu)如下:
2.3.2.第二部分:創(chuàng)建索引
步驟說明:
(1)采集數(shù)據(jù)
(2)將數(shù)據(jù)轉(zhuǎn)換成Lucene文檔
(3)將文檔寫入索引庫横殴,創(chuàng)建索引
2.3.2.1.第一步:采集數(shù)據(jù)
Lucene全文檢索,不是直接查詢數(shù)據(jù)庫卿拴,所以需要先將數(shù)據(jù)采集出來衫仑。
(1)創(chuàng)建Book類
public class Book {
private Integer bookId; // 圖書ID
private String name; // 圖書名稱
private Float price; // 圖書價(jià)格
private String pic; // 圖書圖片
private String description; // 圖書描述
// 補(bǔ)全get\set方法
}
(2)創(chuàng)建一個(gè)BookDao類
package cn.gzsxt.lucene.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import cn.gzsxt.lucene.pojo.Book;
public class BookDao {
public List<Book> getAll() {
// 數(shù)據(jù)庫鏈接
Connection connection = null;
// 預(yù)編譯statement
PreparedStatement preparedStatement = null;
// 結(jié)果集
ResultSet resultSet = null;
// 圖書列表
List<Book> list = new ArrayList<Book>();
try {
// 加載數(shù)據(jù)庫驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
// 連接數(shù)據(jù)庫
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/lucene", "root", "gzsxt");
// SQL語句
String sql = "SELECT * FROM book";
// 創(chuàng)建preparedStatement
preparedStatement = connection.prepareStatement(sql);
// 獲取結(jié)果集
resultSet = preparedStatement.executeQuery();
// 結(jié)果集解析
while (resultSet.next()) {
Book book = new Book();
book.setBookId(resultSet.getInt("id"));
book.setName(resultSet.getString("name"));
book.setPrice(resultSet.getFloat("price"));
book.setPic(resultSet.getString("pic"));
book.setDescription(resultSet.getString("description"));
list.add(book);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(null!=resultSet){
try {
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(null!=preparedStatement){
try {
preparedStatement.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(null!=connection){
try {
connection.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return list;
}
}
(3)創(chuàng)建一個(gè)測(cè)試類BookDaoTest
package cn.gzsxt.lucene.test;
import java.util.List;
import org.junit.Test;
import cn.gzsxt.lucene.dao.BookDao;
import cn.gzsxt.lucene.pojo.Book;
public class BookDaoTest {
@Test
public void getAll(){
BookDao dao = new BookDao();
List<Book> books = dao.getAll();
for (Book book : books) {
System.out.println("圖書id:"+book.getBookId()+",圖書名稱:"+book.getName());
}
}
}
(4)測(cè)試結(jié)果,采集數(shù)據(jù)成功
2.3.2.2.第二步:將數(shù)據(jù)轉(zhuǎn)換成Lucene文檔
Lucene是使用文檔類型來封裝數(shù)據(jù)的堕花,所有需要先將采集的數(shù)據(jù)轉(zhuǎn)換成文檔類型文狱。其格式為:
修改BookDao,新增一個(gè)方法缘挽,轉(zhuǎn)換數(shù)據(jù)
public List<Document> getDocuments(List<Book> books){
// Document對(duì)象集合
List<Document> docList = new ArrayList<Document>();
// Document對(duì)象
Document doc = null;
for (Book book : books) {
// 創(chuàng)建Document對(duì)象如贷,同時(shí)要?jiǎng)?chuàng)建field對(duì)象
doc = new Document();
// 根據(jù)需求創(chuàng)建不同的Field
Field id = new TextField("id", book.getBookId().toString(), Store.YES);
Field name = new TextField("name", book.getName(), Store.YES);
Field price = new TextField("price", book.getPrice().toString(),Store.YES);
Field pic = new TextField("pic", book.getPic(), Store.YES);
Field desc = new TextField("description", book.getDescription(), Store.YES);
// 把域(Field)添加到文檔(Document)中
doc.add(id);
doc.add(name);
doc.add(price);
doc.add(pic);
doc.add(desc);
docList.add(doc);
}
return docList;
}
2.3.2.3.第三步:創(chuàng)建索引庫
說明:Lucene是在將文檔寫入索引庫的過程中陷虎,自動(dòng)完成分詞到踏、創(chuàng)建索引的杠袱。因此創(chuàng)建索引庫,從形式上看窝稿,就是將文檔寫入索引庫楣富!
修改測(cè)試類,新增createIndex方法
@Test
public void createIndex(){
try {
BookDao dao = new BookDao();
// 分析文檔伴榔,對(duì)文檔中的field域進(jìn)行分詞
Analyzer analyzer = new StandardAnalyzer();
// 創(chuàng)建索引
// 1) 創(chuàng)建索引庫目錄
Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));
// 2) 創(chuàng)建IndexWriterConfig對(duì)象
IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST, analyzer);
// 3) 創(chuàng)建IndexWriter對(duì)象
IndexWriter writer = new IndexWriter(directory, cfg);
// 4) 通過IndexWriter對(duì)象添加文檔對(duì)象(document)
writer.addDocuments(dao.getDocuments(dao.getAll()));
// 5) 關(guān)閉IndexWriter
writer.close();
System.out.println("創(chuàng)建索引庫成功");
} catch (Exception e) {
e.printStackTrace();
}
}
測(cè)試結(jié)果纹蝴,創(chuàng)建成功!W偕佟塘安!
2.3.3.第三部分:搜索索引
2.3.3.1.說明
搜索的時(shí)候,需要指定搜索哪一個(gè)域(也就是字段)援奢,并且兼犯,還要對(duì)搜索的關(guān)鍵詞做分詞處理。
2.3.3.2.執(zhí)行搜索
修改測(cè)試類集漾,新增searchDocumentByIndex方法
@Test
public void searchDocumentByIndex(){
try {
// 1切黔、 創(chuàng)建查詢(Query對(duì)象)
// 創(chuàng)建分析器
Analyzer analyzer = new StandardAnalyzer();
QueryParser queryParser = new QueryParser("name", analyzer);
Query query = queryParser.parse("name:java教程");
// 2、 執(zhí)行搜索
// a) 指定索引庫目錄
Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));
// b) 創(chuàng)建IndexReader對(duì)象
IndexReader reader = DirectoryReader.open(directory);
// c) 創(chuàng)建IndexSearcher對(duì)象
IndexSearcher searcher = new IndexSearcher(reader);
// d) 通過IndexSearcher對(duì)象執(zhí)行查詢索引庫具篇,返回TopDocs對(duì)象
// 第一個(gè)參數(shù):查詢對(duì)象
// 第二個(gè)參數(shù):最大的n條記錄
TopDocs topDocs = searcher.search(query, 10);
// e) 提取TopDocs對(duì)象中前n條記錄
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
System.out.println("查詢出文檔個(gè)數(shù)為:" + topDocs.totalHits);
for (ScoreDoc scoreDoc : scoreDocs) {
// 文檔對(duì)象ID
int docId = scoreDoc.doc;
Document doc = searcher.doc(docId);
// f) 輸出文檔內(nèi)容
System.out.println("===============================");
System.out.println("文檔id:" + docId);
System.out.println("圖書id:" + doc.get("id"));
System.out.println("圖書name:" + doc.get("name"));
System.out.println("圖書price:" + doc.get("price"));
System.out.println("圖書pic:" + doc.get("pic"));
System.out.println("圖書description:" + doc.get("description"));
}
// g) 關(guān)閉IndexReader
reader.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
測(cè)試結(jié)果纬霞,非常成功!G浴诗芜!
2.4.小結(jié)
Lucene全文檢索,確實(shí)可以實(shí)現(xiàn)對(duì)關(guān)鍵詞做分詞埃疫、再執(zhí)行搜索功能伏恐。并且結(jié)果更精確。
3.分詞
3.1.重要性
分詞是全文檢索的核心熔恢。
所謂的分詞脐湾,就是將一段文本,根據(jù)一定的規(guī)則叙淌,拆分成一個(gè)一個(gè)詞秤掌。
Lucene是根據(jù)分析器實(shí)現(xiàn)分詞的。針對(duì)不同的語言提供了不同的分析器鹰霍。并且提供了一個(gè)通用的標(biāo)準(zhǔn)分析器StandardAnalyzer
3.2.分詞過程
--說明:我們通過分析StandardAnalyzer核心源碼來分析分詞過程
@Override
protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
final StandardTokenizer src = new StandardTokenizer(getVersion(), reader);
src.setMaxTokenLength(maxTokenLength);
TokenStream tok = new StandardFilter(getVersion(), src);
tok = new LowerCaseFilter(getVersion(), tok);
tok = new StopFilter(getVersion(), tok, stopwords);
return new TokenStreamComponents(src, tok) {
@Override
protected void setReader(final Reader reader) throws IOException {
src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);
super.setReader(reader);
}
};
}
對(duì)應(yīng)Lucene分詞的過程闻鉴,我們可以做如下總結(jié):
(1)分詞的時(shí)候,是以域?yàn)閱挝坏拿鳌2煌挠蛎系海嗷オ?dú)立。
同一個(gè)域中,拆分出來相同的詞渠羞,視為同一個(gè)詞(Term)
不同的域中斤贰,拆分出來相同的詞,不是同一個(gè)詞次询。
其中荧恍,Term是Lucene最小的語匯單元,不可再細(xì)分屯吊。
(2)分詞的時(shí)候經(jīng)歷了一系列的過濾器送巡。如大小寫轉(zhuǎn)換、去除停用詞等盒卸。
3.3.分詞后索引庫結(jié)構(gòu)
我們這里借助前面的示例來說明
從上圖中骗爆,我們發(fā)現(xiàn):
(1)索引庫中有兩個(gè)區(qū)域:索引區(qū)、文檔區(qū)蔽介。
(2)文檔區(qū)存放的是文檔摘投。Lucene給每一個(gè)文檔自動(dòng)加上一個(gè)文檔編號(hào)docID。
(3)索引區(qū)存放的是索引屉佳。注意:
索引是以域?yàn)閱挝坏墓瘸煌挠颍舜讼嗷オ?dú)立武花。
索引是根據(jù)分詞規(guī)則創(chuàng)建出來的圆凰,根據(jù)索引就能找到對(duì)應(yīng)的文檔。
3.4.Luke客戶端連接索引庫
Luke作為Lucene工具包中的一個(gè)工具(http://www.getopt.org/luke/)体箕,可以通過可視化界面专钉,連接操作索引庫。
3.4.1.啟動(dòng)方法
(1)雙擊start.bat啟動(dòng)累铅!
(2)連接索引庫
3.4.2.驗(yàn)證分詞效果
4.Field域
問題:我們已經(jīng)知道跃须,Lucene是在寫入文檔時(shí),完成分詞娃兽、索引的菇民。那Lucene是怎么知道的呢?
答:Lucene是根據(jù)文檔中的域的屬性投储,來確定是否要分詞第练、創(chuàng)建索引的。所以玛荞,我們必須搞清楚域有哪些屬性娇掏。
4.1.域的屬性
4.1.1.三大屬性
4.1.1.1.是否分詞(tokenized)
只有設(shè)置了分詞屬性為true,lucene才會(huì)對(duì)這個(gè)域進(jìn)行分詞處理勋眯。
在實(shí)際的開發(fā)中婴梧,有一些字段是不需要分詞的下梢,比如商品id,商品圖片等塞蹭。
而有一些字段是必須分詞的孽江,比如商品名稱,描述信息等浮还。
4.1.1.2.是否索引(indexed)
只有設(shè)置了索引屬性為true竟坛,lucene才為這個(gè)域的Term詞創(chuàng)建索引。
在實(shí)際的開發(fā)中钧舌,有一些字段是不需要?jiǎng)?chuàng)建索引的,比如商品的圖片等涎跨。我們只需要對(duì)參與搜索的字段做索引處理洼冻。
4.1.1.3.是否存儲(chǔ)(stored)
只有設(shè)置了存儲(chǔ)屬性為true,在查找的時(shí)候隅很,才能從文檔中獲取這個(gè)域的值撞牢。
在實(shí)際開發(fā)中,有一些字段是不需要存儲(chǔ)的叔营。比如:商品的描述信息屋彪。
因?yàn)樯唐访枋鲂畔ⅲǔ6际谴笪谋緮?shù)據(jù)绒尊,讀的時(shí)候會(huì)造成巨大的IO開銷畜挥。而描述信息是不需要經(jīng)常查詢的字段,這樣的話就白白浪費(fèi)了cpu的資源了婴谱。
因此蟹但,像這種不需要經(jīng)常查詢,又是大文本的字段谭羔,通常不會(huì)存儲(chǔ)到索引庫华糖。
4.1.2.特點(diǎn)
(1)三大屬性彼此獨(dú)立。
(2)通常分詞是為了創(chuàng)建索引瘟裸。
(3)不存儲(chǔ)這個(gè)域文本內(nèi)容客叉,也可以對(duì)這個(gè)域先分詞、創(chuàng)建索引话告。
4.2.Field常用類型
域的常用類型有很多兼搏,每一個(gè)類都有自己默認(rèn)的三大屬性。如下:
4.3.改造入門示例中的域類型
4.3.1.分析
(1)圖書id:
是否分詞:不用分詞超棺,因?yàn)椴粫?huì)根據(jù)商品id來搜索商品
是否索引:不索引向族,因?yàn)椴恍枰鶕?jù)圖書ID進(jìn)行搜索
是否存儲(chǔ):要存儲(chǔ),因?yàn)椴樵兘Y(jié)果頁面需要使用id這個(gè)值棠绘。
(2)圖書名稱:
是否分詞:要分詞件相,因?yàn)橐獙D書的名稱內(nèi)容分詞索引再扭,根據(jù)關(guān)鍵搜索圖書名稱抽取的詞。
是否索引:要索引夜矗。
是否存儲(chǔ):要存儲(chǔ)泛范。
(3)圖書價(jià)格:
是否分詞:要分詞,lucene對(duì)數(shù)字型的值只要有搜索需求的都要分詞和索引紊撕,因?yàn)閘ucene對(duì)數(shù)字型的內(nèi)容要特殊分詞處理罢荡,本例子可能要根據(jù)價(jià)格范圍搜索, 需要分詞和索引对扶。
是否索引:要索引
是否存儲(chǔ):要存儲(chǔ)
(4)圖書圖片地址:
是否分詞:不分詞
是否索引:不索引
是否存儲(chǔ):要存儲(chǔ)
(5)圖書描述:
是否分詞:要分詞
是否索引:要索引
是否存儲(chǔ):因?yàn)閳D書描述內(nèi)容量大区赵,不在查詢結(jié)果頁面直接顯示,不存儲(chǔ)浪南。
不存儲(chǔ)是來不在lucene的索引文件中記錄笼才,節(jié)省lucene的索引文件空間,如果要在詳情頁面顯示描述络凿,思路:
從lucene中取出圖書的id骡送,根據(jù)圖書的id查詢關(guān)系數(shù)據(jù)庫中book表得到描述信息。
4.3.2.代碼修改
修改BookDao的getDocument方法
public List<Document> getDocuments(List<Book> books){
// Document對(duì)象集合
List<Document> docList = new ArrayList<Document>();
// Document對(duì)象
Document doc = null;
for (Book book : books) {
// 創(chuàng)建Document對(duì)象絮记,同時(shí)要?jiǎng)?chuàng)建field對(duì)象
doc = new Document();
// 圖書ID
// 參數(shù):域名摔踱、域中存儲(chǔ)的內(nèi)容、是否存儲(chǔ)
// 不分詞怨愤、索引派敷、要存儲(chǔ)
// Field id = new TextField("id", book.getId().toString(),Store.YES);
Field id = new StoredField("id", book.getBookId().toString());
// 圖書名稱
// 分詞、索引憔四、存儲(chǔ)
Field name = new TextField("name", book.getName(),Store.YES);
// 圖書價(jià)格
// 分詞膀息、索引、存儲(chǔ)
Field price = new FloatField("price", book.getPrice(), Store.YES);
// 圖書圖片
// 不分詞了赵、不索引潜支、要存儲(chǔ)
Field pic = new StoredField("pic", book.getPic());
// 圖書描述
// 分詞、索引柿汛、不存儲(chǔ)
Field desc = new TextField("description",book.getDescription(), Store.NO);
// 把域(Field)添加到文檔(Document)中
doc.add(id);
doc.add(name);
doc.add(price);
doc.add(pic);
doc.add(desc);
docList.add(doc);
}
return docList;
}
4.3.3.測(cè)試
(1)去索引庫目錄中冗酿,手動(dòng)清空索引庫。
(2)重新創(chuàng)建索引庫络断。
(3)使用Luke驗(yàn)證分詞裁替、索引效果。
改造成功C脖俊H跖小!
5.索引庫維護(hù)
在第4節(jié)锥惋,我們需要重新創(chuàng)建索引的時(shí)候昌腰,是去索引庫目錄下开伏,手動(dòng)刪除的。
而在實(shí)際的開發(fā)中遭商,我們可能壓根就不知道索引庫在哪固灵,就算知道,我們也不可能每次都去手動(dòng)刪除劫流,非常之麻煩N撞!!祠汇!
所以仍秤,我們必須學(xué)習(xí)如何維護(hù)索引庫,使用程序來操作索引庫座哩。
需要注意的是徒扶,索引是與文檔緊密相連的,因此對(duì)索引的維護(hù)根穷,實(shí)際上就是對(duì)文檔的增刪改。
5.1.添加索引(文檔)
5.1.1.需求
數(shù)據(jù)庫中新上架了圖書导坟,必須把這些圖書也添加到索引庫中屿良,不然就搜不到該新上架的圖書了。
5.1.2.代碼實(shí)現(xiàn)
調(diào)用 indexWriter.addDocument(doc)添加索引惫周。
參考入門示例中的創(chuàng)建索引尘惧。
5.2.刪除索引(文檔)
5.2.1.需求
某些圖書不再出版銷售了,我們需要從索引庫中移除該圖書递递。
5.2.1.代碼實(shí)現(xiàn)
@Test
public void deleteIndex() throws Exception {
// 1喷橙、指定索引庫目錄
Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));
// 2、創(chuàng)建IndexWriterConfig
IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
new StandardAnalyzer());
// 3登舞、 創(chuàng)建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// 4贰逾、通過IndexWriter來刪除索引
// 刪除指定索引
writer.deleteDocuments(new Term("name", "apache"));
// 5、關(guān)閉IndexWriter
writer.close();
System.out.println("刪除成功");
}
5.2.3.清空索引庫
@Test
public void deleteIndex() throws Exception {
// 1菠秒、指定索引庫目錄
Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));
// 2疙剑、創(chuàng)建IndexWriterConfig
IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
new StandardAnalyzer());
// 3、 創(chuàng)建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// 4践叠、通過IndexWriter來刪除索引
// 刪除指定索引
writer.deleteAll();
// 5言缤、關(guān)閉IndexWriter
writer.close();
System.out.println("清空索引庫成功");
}
5.3.更新索引(文檔)
5.3.1.說明
Lucene更新索引比較特殊,是先刪除滿足條件的索引禁灼,再添加新的索引管挟。
5.3.2.代碼實(shí)現(xiàn)
// 修改索引
@Test
public void updateIndex() throws Exception {
// 1、指定索引庫目錄
Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));
// 2弄捕、創(chuàng)建IndexWriterConfig
IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
new StandardAnalyzer());
// 3僻孝、 創(chuàng)建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// 4导帝、通過IndexWriter來修改索引
// a)、創(chuàng)建修改后的文檔對(duì)象
Document document = new Document();
// 文件名稱
Field filenameField = new StringField("name", "updateIndex", Store.YES);
document.add(filenameField);
// 修改指定索引為新的索引
writer.updateDocument(new Term("name", "apache"), document);
// 5皮璧、關(guān)閉IndexWriter
writer.close();
System.out.println("更新成功");
}
6.搜索
問題:我們?cè)谌腴T示例中舟扎,已經(jīng)知道Lucene是通過IndexSearcher對(duì)象,來執(zhí)行搜索的悴务。那我們?yōu)槭裁催€要繼續(xù)學(xué)習(xí)Lucene呢睹限?
答:因?yàn)樵趯?shí)際的開發(fā)中,我們的查詢的業(yè)務(wù)是相對(duì)復(fù)雜的讯檐,比如我們?cè)谕ㄟ^關(guān)鍵詞查找的時(shí)候羡疗,往往進(jìn)行價(jià)格、商品類別的過濾别洪。
而Lucene提供了一套查詢方案叨恨,供我們實(shí)現(xiàn)復(fù)雜的查詢。
6.1.創(chuàng)建查詢的兩種方法
執(zhí)行查詢之前挖垛,必須創(chuàng)建一個(gè)查詢Query查詢對(duì)象痒钝。
Query自身是一個(gè)抽象類,不能實(shí)例化痢毒,必須通過其它的方式來實(shí)現(xiàn)初始化送矩。
在這里,Lucene提供了兩種初始化Query查詢對(duì)象的方式哪替。
6.1.1.使用Lucene提供Query子類
Query是一個(gè)抽象類栋荸,lucene提供了很多查詢對(duì)象,比如TermQuery項(xiàng)精確查詢凭舶,NumericRangeQuery數(shù)字范圍查詢等晌块。
使用TermQuery實(shí)例化
Query query = new TermQuery(new Term("name", "lucene"));
6.1.2.使用QueryParse解析查詢表達(dá)式
QueryParser會(huì)將用戶輸入的查詢表達(dá)式解析成Query對(duì)象實(shí)例。如下代碼:
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
Query query = queryParser.parse("name:lucene");
6.2.常用的Query子類搜索
6.2.1.TermQuery
特點(diǎn):查詢的關(guān)鍵詞不會(huì)再做分詞處理帅霜,作為整體來搜索匆背。代碼如下:
/**
* Query子類查詢之 TermQuery
*
* 特點(diǎn):不會(huì)再對(duì)查詢的關(guān)鍵詞做分詞處理。
*
* 需要:查詢書名與java教程相關(guān)書义屏。
*/
@Test
public void queryByTermQuery(){
//1靠汁、獲取一個(gè)查詢對(duì)象
Query query = new TermQuery(new Term("name", "編程思想"));
doSearch(query);
}
private void doSearch(Query query) {
try {
//2、創(chuàng)建一個(gè)查詢的執(zhí)行對(duì)象
//指定索引庫的目錄
Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));
//創(chuàng)建流對(duì)象
IndexReader reader = DirectoryReader.open(d);
//創(chuàng)建搜索執(zhí)行對(duì)象
IndexSearcher searcher = new IndexSearcher(reader);
//3闽铐、執(zhí)行搜索
TopDocs result = searcher.search(query, 10);
//4蝶怔、提出結(jié)果集,獲取圖書的信息
int totalHits = result.totalHits;
System.out.println("共查詢到"+totalHits+"條滿足條件的數(shù)據(jù)!");
System.out.println("-----------------------------------------");
//提取圖書信息兄墅。
//score即相關(guān)度踢星。即搜索的關(guān)鍵詞和 圖書名稱的相關(guān)度,用來做排序處理
ScoreDoc[] scoreDocs = result.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
/**
* scoreDoc.doc的返回值隙咸,是文檔的id沐悦, 即 將文檔寫入索引庫的時(shí)候成洗,lucene自動(dòng)給這份文檔做的一個(gè)編號(hào)。
*
* 獲取到這個(gè)文檔id之后藏否,即可以根據(jù)這個(gè)id瓶殃,找到這份文檔。
*/
int docId = scoreDoc.doc;
System.out.println("文檔在索引庫中的編號(hào):"+docId);
//從文檔中提取圖書的信息
Document doc = searcher.doc(docId);
System.out.println("圖書id:"+doc.get("id"));
System.out.println("圖書name:"+doc.get("name"));
System.out.println("圖書price:"+doc.get("price"));
System.out.println("圖書pic:"+doc.get("pic"));
System.out.println("圖書description:"+doc.get("description"));
System.out.println();
System.out.println("------------------------------------");
}
//關(guān)閉連接副签,釋放資源
if(null!=reader){
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
6.2.2.NumericRangeQuery
指定數(shù)字范圍查詢.(創(chuàng)建field類型時(shí)遥椿,注意與之對(duì)應(yīng))
/**
* Query子類查詢 之 NumricRangeQuery
* 需求:查詢所有價(jià)格在[60,80)之間的書
* @param query
*/
@Test
public void queryByNumricRangeQuery(){
/**
* 第一個(gè)參數(shù):要搜索的域
* 第二個(gè)參數(shù):最小值
* 第三個(gè)參數(shù):最大值
* 第四個(gè)參數(shù):是否包含最小值
* 第五個(gè)參數(shù):是否包含最大值
*/
Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);
doSearch(query);
}
###6.2.3.BooleanQuery
BooleanQuery,布爾查詢淆储,實(shí)現(xiàn)組合條件查詢冠场。
/**
* Query子類查詢 之 BooelanQuery查詢 組合條件查詢
*
* 需求:查詢書名包含java,并且價(jià)格區(qū)間在[60,80)之間的書本砰。
*/
@Test
public void queryBooleanQuery(){
//1碴裙、要使用BooelanQuery查詢,首先要把單個(gè)創(chuàng)建出來点额,然后再通過BooelanQuery組合
Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);
Query name = new TermQuery(new Term("name", "java"));
//2舔株、創(chuàng)建BooleanQuery實(shí)例對(duì)象
BooleanQuery query = new BooleanQuery();
query.add(name, Occur.MUST_NOT);
query.add(price, Occur.MUST);
/**
* MSUT 表示必須滿足 對(duì)應(yīng)的是 +
* MSUT_NOT 必須不滿足 應(yīng)對(duì)的是 -
* SHOULD 可以滿足也可以不滿足 沒有符號(hào)
*
* SHOULD 與MUST、MUST_NOT組合的時(shí)候还棱,SHOULD就沒有意義了督笆。
*/
doSearch(query);
}
6.3.通過QueryParser搜索
6.3.1.特點(diǎn)
對(duì)搜索的關(guān)鍵詞,做分詞處理诱贿。
6.3.2.語法
6.3.2.1.基礎(chǔ)語法
域名:關(guān)鍵字
實(shí)例:name:java
6.3.2.2.組合條件語法
條件1 AND 條件2
條件1 OR 條件2
條件1 NOT 條件2
6.3.3.QueryParser
6.3.3.1.代碼實(shí)現(xiàn)
/**
* 查詢解析器查詢 之 QueryParser查詢
*/
@Test
public void queryByQueryParser(){
try {
//1、加載分詞器
Analyzer analyzer = new StandardAnalyzer();
/**
* 2咕缎、創(chuàng)建查詢解析器實(shí)例對(duì)象
* 第一個(gè)參數(shù):默認(rèn)搜索的域珠十。
* 如果在搜索的時(shí)候,沒有特別指定搜索的域凭豪,則按照默認(rèn)的域進(jìn)行搜索
* 如何在搜索的時(shí)候指定搜索域呢焙蹭?
* 答:格式 域名:關(guān)鍵詞 即 name:java教程
*
* 第二個(gè)參數(shù):分詞器 ,對(duì)關(guān)鍵詞做分詞處理
*/
QueryParser parser = new QueryParser("description", analyzer);
Query query = parser.parse("name:java教程");
doSearch(query);
} catch (Exception e) {
e.printStackTrace();
}
}
6.3.4.MultiFieldQueryParser
通過MulitFieldQueryParse對(duì)多個(gè)域查詢嫂伞。
/**
* 查詢解析器查詢 之 MultiFieldQueryParser查詢
*
* 特點(diǎn):同時(shí)指定多個(gè)搜索域孔厉,并且對(duì)關(guān)鍵做分詞處理
*/
@Test
public void queryByMultiFieldQueryParser(){
try {
//1、定義多個(gè)搜索的 name帖努、description
String[] fields = {"name","description"};
//2撰豺、加載分詞器
Analyzer analyzer = new StandardAnalyzer();
//3、創(chuàng)建 MultiFieldQueryParser實(shí)例對(duì)象
MultiFieldQueryParser mParser = new MultiFieldQueryParser(fields, analyzer);
Query query = mParser.parse("lucene教程");
doSearch(query);
} catch (Exception e) {
e.printStackTrace();
}
}