SpringBoot整合Lucene實現全文檢索的詳細步驟
1. 項目背景
本文的出現是我的個人網站搭建的過程中產生的,作為一個技術博客為主的網站,Mysql的搜索已經滿足不了我的野心了,于是,我便瞄上了全文檢索。最初,是打算直接使用比較熟悉的ES,但是考慮到部署ES額外的服務器資源開銷,最后選擇了Lucene,搭配IK分詞器,直接在項目中整合。
2. 什么是Lucene
看看官網上的介紹吧~
Apache Lucene™ is a high-performance, full-featured search engine library written entirely in Java. It is a technology suitable for nearly any application that requires structured search, full-text search, faceting, nearest-neighbor search across high-dimensionality vectors, spell correction or query suggestions.
Apache Lucene is an open source project available for free download.
看不懂,翻譯過來就是:
Apache Lucene™是一個完全用Java編寫的高性能、全功能的搜索引擎庫。它是一種幾乎適用于任何需要結構化搜索、全文搜索、切面、跨高維向量的最近鄰搜索、拼寫糾正或查詢建議的應用程序的技術。Apache Lucene是一個免費下載的開源項目。
沒錯,它就是我們需要的全文搜索引擎,接下來讓我們一起看看怎么在SpringBoot項目中集成使用它吧。
3. 引入依賴,配置索引
3.1 引入Lucene依賴和分詞器依賴
先看看需要的依賴吧。
算了,還是先說說我的需求吧,算了,沒有需求,具體參考百度搜索框吧~反正就是那樣
直接上依賴吧,默認分詞器對中文不友好。這里使用IK分詞器
<!-- Lucene核心庫 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>7.6.0</version> </dependency> <!-- Lucene的查詢解析器 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>7.6.0</version> </dependency> <!-- Lucene的默認分詞器庫 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>7.6.0</version> </dependency> <!-- Lucene的高亮顯示 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>7.6.0</version> </dependency> <!-- ik分詞器 --> <dependency> <groupId>com.jianggujin</groupId> <artifactId>IKAnalyzer-lucene</artifactId> <version>8.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>com.janeluo</groupId>--> <!-- <artifactId>ikanalyzer</artifactId>--> <!-- <version>2012_u6</version>--> <!-- </dependency>-->
這里使用com.jianggujin:IKAnalyzer-lucene:8.0.0
可以兼容新版本的lucene。
新版本的lucene和com.janeluo:ikanalyzer:2012_u6
版本沖突,會報以下錯誤。
解決方案放在源碼中了,這里不展開了。使用com.janeluo:ikanalyzer:2012_u6
版本,把com.maple.lucene.util.MyIKAnalyzer
和MyIKTokenizer
的注釋放開就行。
3.2 表結構和數據準備
準備表結構,這里是簡化過的表結構,只提供演示效果。
CREATE TABLE `blog_title` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `title` VARCHAR(255) NOT NULL COMMENT '標題', `description` VARCHAR(255) NULL DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) USING BTREE ) COMMENT='博客標題' COLLATE='utf8_general_ci' ENGINE=InnoDB;
準備測試數據:
INSERT INTO `blog_title` (`id`, `title`, `description`) VALUES (808, '0.SpringBoot目錄', 'https://xiaoxiaofeng.com'), (809, '1.SpringBoot項目創(chuàng)建', '大家好,我是笑小楓,跟我一起玩轉SpringBoot項目吧,本文講一下如何搭建SpringBoot項目。'), (810, '10.SpringBoot處理請求跨域問題', 'CORS全稱Cross-Origin Resource Sharing,意為跨域資源共享。當一個資源去訪問另一個不同域名或者同域名不同端口的資源時,就會發(fā)出跨域請求。如果此時另一個資源不允許其進行跨域資源訪問,那么訪問就會遇到跨域問題??缬蛑傅氖怯捎跒g覽器的安全性限制,不允許前端頁面訪問協(xié)議不同、域名不同、端口號不同的http接口。'), (811, '11.SpringBoot接口日志信息統(tǒng)一記錄', '為什么要記錄接口日志?\n至于為什么,詳細看到這里的小伙伴心里都有一個答案吧,我這里簡單列一下常用的場景吧用戶登錄記錄統(tǒng)計、重要增刪改操作留痕、需要統(tǒng)計用戶的訪問次數、接口調用情況統(tǒng)計、線上問題排查、等等等...既然有這么多使用場景,那我們該怎么處理,總不能一條一條的去記錄吧面試是不是老是被問Spring的Aop的使用場景,那這個典型的場景就來了,我們可以使用Spring的Aop,完美的實現這個功能,接下來上代碼??'), (812, '12.SpringBoot導入Excel', '在java處理excel方便從簡單的實現功能到自己封裝工具類,一路走了好多,阿里的easyExcel對POI的封裝更加精簡這里介紹一下簡單使用。'), (813, '13.SpringBoot導出Excel', '在java處理excel方便從簡單的實現功能到自己封裝工具類,一路走了好多,阿里的easyExcel對POI的封裝更加精簡這里介紹一下簡單使用。'), (814, '14.SpringBoot發(fā)送郵件', '本文主要介紹了使用SpringBoot發(fā)送郵件,主要包含如何獲取發(fā)送郵件的授權碼,這里以QQ郵箱為例,然后介紹了功能如何實現,包括通過模板發(fā)送郵件,發(fā)送帶圖片的郵件,發(fā)送帶附件的郵件,發(fā)送帶有多個附件的郵件。'), (815, '15.SpringBoot根據模板生成Word', '本文主要講了SpringBoot基于模板的形式生成word的功能實現,感興趣或有類似功能需求的小伙伴可以看一下,包括word模板制作,功能代碼實現,支持導出圖片、表格等功能。'), (816, '16.SpringBoot生成PDF', '本文主要介紹了在SpringBoot項目下,通過代碼和操作步驟,詳細的介紹了如何操作PDF。希望可以幫助到準備通過JAVA操作PDF的你。\n本文涉及pdf操作,如下:\nPDF模板制作、 基于PDF模板生成,并支持下載、自定義中文字體、完全基于代碼生成,并保存到指定目錄、合并PDF,并保存到指定目錄、合并PDF,并支持下載\n'), (817, '17.SpringBoot文件上傳下載', '在java開發(fā)中文件的上傳、下載、刪除功能肯定是很常見的,本文主要基于上傳圖片或文件到指定的位置展開,通過詳細的代碼和工具類,講述java如何實現文件的上傳、下載、刪除。'), (818, '18.SpringBoot中的Properties配置', 'springboot在使用過程中,我們有很多配置,比如mysql配置、redis配置、mybatis-plus、調用第三方的接口配置等等...\n\n我們現在都是放在一個大而全的配置里面的,如果我們想根據功能分為不同的配置文件管理,讓配置更加清晰,應該怎么做呢?'), (819, '19.使用Docker部署最佳實踐', '使用Docker部署最佳實踐'), (820, '2.SpringBoot配置基于swagger2的knife4j接口文檔', 'SpringBoot項目如果前后端分離,怎么把寫好了的接口返回給前端的小伙伴呢,試試這款基于Swagger2的knife4j吧,簡直好用到爆!'), (821, '3.SpringBoot集成Mybatis Plus', '本文主要介紹了SpringBoot集成mysql數據庫、集成Mybatis Plus框架;通過一個簡單的例子演示了一下使用Mybatis Plus進行數據插入和查詢;使用Knife4j進行接口調試;集成阿里巴巴Druid數據連接池;通過Druid頁面進行執(zhí)行sql查詢、分析。'), (822, '4.SpringBoot返回統(tǒng)一結果包裝', '前后端分離的時代,如果沒有統(tǒng)一的返回格式,給前端的結果各式各樣,估計前端的小伙伴就要罵娘了。 \n我們想對自定義異常拋出指定的狀態(tài)碼排查錯誤,對系統(tǒng)的不可預知的異常拋出友好一點的異常信息。 \n我們想讓接口統(tǒng)一返回一些額外的數據,例如接口執(zhí)行的時間等等。 那就進來一起康康吧~......'), (823, '5.SpringBoot返回統(tǒng)一異常處理', '如果程序拋異常了,我們是否也可以返回統(tǒng)一的格式呢?\n答案是,當然可以的,不光可以拋出我們想要的格式,還可以對指定的異常類型進行特殊處理\n例如使用@Validated對入參校驗的異常,我們自定義的異常等等...'), (824, '6.SpringBoot日志打印Logback詳解', 'Logback 旨在作為流行的 log4j 項目的繼承者,是SpringBoot內置的日志處理框架,spring-boot-starter其中包含了spring-boot-starter-logging,該依賴內容就是 Spring Boot 默認的日志框架 logback。這里給大家介紹一下在SpraingBoot中Logback的配置。'), (825, '7.SpringBoot控制臺自定義banner', '熬夜整理完logback相關的內容,突然發(fā)現我們的《笑小楓系列-玩轉SpringBoot》已經6篇文章了,我們的配套程序居然沒有一個屬于自己的log,這簡直說不過去了,我這處女座的小暴脾氣,趕緊整一個,于是便有了此文。好了,接下來言歸正傳,畢竟本文也是屬于我們系列的一份子嘛,不能落下'), (826, '8.SpringBoot集成Redis', 'SpringBoot中怎么使用Redis做緩存機制呢?本文為大家揭開Redis的面紗,內容偏基礎,但詳細。本文核心:SpringBoot繼承redis、SpringBoot常用的redis操作演示、監(jiān)聽Redis的key過期機制。'), (827, '9.SpringBoot用戶登錄攔截器', '本文主要介紹了SpringBoot實現登錄功能,使用JWT+Redis進行功能實現,從最基礎的建表開始,詳細的介紹了功能的實現。學習完本文,你將掌握登錄功能的核心技能。'), (832, '【笑小楓的按步照搬系列】JDK8下載安裝配置', '本文主要講解了JDK8在windows環(huán)境下的下載、安裝、已經環(huán)境變量的配置,參照本文,你只需要按步照搬,便可快速的安裝好JAVA環(huán)境。'), (833, '【笑小楓的按步照搬系列】Maven環(huán)境配置', '本文主要介紹了maven的安裝配置,包括配置本地倉庫,配置阿里鏡像等。安裝maven環(huán)境之前要先安裝java jdk環(huán)境(沒有安裝java環(huán)境的可以先去看安裝JAVA環(huán)境的教程)Maven 3.3+ require JDK 1.7 及以上。'), (834, '【笑小楓的按步照搬系列】Node.js安裝', 'Node.js安裝'), (835, '【笑小楓的按步照搬系列】Redis可視化工具-RedisInsight', 'RedisInsight是Redis官方出品的可視化管理工具,可用于設計、開發(fā)、優(yōu)化你的Redis應用。支持深色和淺色兩種主題,界面非常炫酷!可支持String、Hash、Set、List、JSON等多種數據類型的管理,同時支持遠程使用CLI功能,功能非常強大!'), (836, '【笑小楓的按步照搬系列】Redis多系統(tǒng)安裝(Windows、Linux、Ubuntu)', 'Redis(Remote Dictionary Server ),即遠程字典服務,是一個開源的使用ANSI C語言編寫、支持網絡、可基于內存亦可持久化的日志型、Key-Value數據庫,并提供多種語言的API。本文主要講述了Redis如何安裝。'), (837, '【笑小楓的按步照搬系列】開源的服務器遠程工具-FinalShell', '之前一直使用 xshell + ftp 組合的方式來部署項目,后來發(fā)現了FinalShell 這款軟件,瞬間就愛上了。FinalShell 相當于 xshell + ftp 的組合,即:FinalShell = xshell + ftp ;FinalShell 只用一個程序,將xshell 、ftp同屏顯示,既可以輸入命令,也可以傳輸數據,還能以樹的形式展示文件路徑。'), (840, '【笑小楓的按步照搬系列】本地安裝Mysql數據庫', '本文主要介紹了在windows環(huán)境下如何下載安裝mysql8+版本,你只需要按步照搬就可以完美解決你安裝軟件的困擾。本文主要包括mysql的下載、安裝、配置my.ini文件、修改初始化密碼等。'), (841, '【笑小楓的按步照搬系列】版本控制工具git安裝過程詳解', 'Git 是個免費的開源分布式版本控制系統(tǒng),下載地址為git-scm.com 或者 gitforwindows.org,本文介紹 Git-2.35.1.2-64-bit.exe 版本的安裝方法,需要的小伙伴可以看一看。');
對數據庫的操作使用的Mybatis Plus,這里演示比較簡單,只是單純的取數據,不貼詳細代碼了,需要的去源碼里面獲取。不想連數據庫可以直接用個List模擬掉,簡單的貼個對象吧。
import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; /** * <p> * blog標題 * </p> * * @author 笑小楓 <https://xiaoxiaofeng.com/> * @since 2023-01-30 */ @Data @TableName("blog_title") public class BlogTitle { private Long id; private String title; private String description; }
3.3 創(chuàng)建索引
這里直接從數據庫查詢所有數據,然后創(chuàng)建索引了,只為演示,實際使用中根據數據量大小,業(yè)務需要哪些字段,是否需要回表查詢等等考慮生產方案,釣無定法,技術多彩
。
直接上代碼了,索引建在d:\\indexDir
目錄下,實際使用該封裝封裝,該放配置放配置哈。這里為了演示效果好(方便你們copy),集中都放在這里了。 注釋比較詳細,不單獨介紹功能了。
如果新增數據追加的話,使用conf.setOpenMode(IndexWriterConfig.OpenMode.APPEND);
模式即可。
@GetMapping("/createIndex") public String createIndex() { List<BlogTitle> list = blogTitleMapper.selectList(Wrappers.lambdaQuery(BlogTitle.class)); // 創(chuàng)建文檔的集合 Collection<Document> docs = new ArrayList<>(); for (BlogTitle blogTitle : list) { // 創(chuàng)建文檔對象 Document document = new Document(); // StringField: 這個 Field 用來構建一個字符串Field,不分析,會索引,Field.Store控制存儲 // LongPoint、IntPoint 等類型存儲數值類型的數據。會分析,會索引,不存儲,如果想存儲數據還需要使用 StoredField // StoredField: 這個 Field 用來構建不同類型,不分析,不索引,會存儲 // TextField: 如果是一個Reader, 會分析,會索引,,Field.Store控制存儲 document.add(new StringField("id", String.valueOf(blogTitle.getId()), Field.Store.YES)); // Field.Store.YES, 將原始字段值存儲在索引中。這對于短文本很有用,比如文檔的標題,它應該與結果一起顯示。 // 值以其原始形式存儲,即在存儲之前沒有使用任何分析器。 document.add(new TextField("title", blogTitle.getTitle(), Field.Store.YES)); // Field.Store.NO,可以索引,分詞,不將字段值存儲在索引中。 // 個人理解:說白了就是為了省空間,如果回表查詢,其實無所謂,如果不回表查詢,需要展示就要保存,設為YES,無需展示,設為NO即可。 document.add(new TextField("description", blogTitle.getDescription(), Field.Store.NO)); docs.add(document); } // 引入IK分詞器,如果需要解決上面版本沖突報錯的問,使用`new MyIKAnalyzer()`即可 Analyzer analyzer = new IKAnalyzer(); // 索引寫出工具的配置對象 IndexWriterConfig conf = new IndexWriterConfig(analyzer); // 設置打開方式:OpenMode.APPEND 會在索引庫的基礎上追加新索引。OpenMode.CREATE會先清空原來數據,再提交新的索引 conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE); // 索引目錄類,指定索引在硬盤中的位置,我的設置為D盤的indexDir文件夾 // 創(chuàng)建索引的寫出工具類。參數:索引的目錄和配置信息 try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); IndexWriter indexWriter = new IndexWriter(directory, conf)) { // 把文檔集合交給IndexWriter indexWriter.addDocuments(docs); // 提交 indexWriter.commit(); } catch (Exception e) { log.error("創(chuàng)建索引失敗", e); return "創(chuàng)建索引失敗"; } return "創(chuàng)建索引成功"; }
創(chuàng)建索引后,在d:\\indexDir
目錄下會出現索引文件,如下圖
3.4 修改索引
數據變更時,索引應該怎么變更呢?該如何怎么設計呢?
- 在程序中數據變更的時候,更新索引,但是對業(yè)務的侵入性比較大。新增、修改、刪除時都要多一套操作Lucene的接口。
- 監(jiān)聽數據庫數據變更,然后更新索引,引入額外中間件,復雜度變高。
有舍有得吧,看權衡點在哪了,大家有什么好的方案可以留言喲。
@GetMapping("/updateIndex") public String update() { // 創(chuàng)建配置對象 IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer()); // 創(chuàng)建目錄對象 // 創(chuàng)建索引寫出工具 try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); IndexWriter writer = new IndexWriter(directory, conf)) { // 獲取更新的數據,這里只是演示 BlogTitle blogTitle = blogTitleMapper.selectById("808"); // 創(chuàng)建新的文檔數據 Document doc = new Document(); doc.add(new StringField("id", "808", Field.Store.YES)); doc.add(new TextField("title", blogTitle.getTitle(), Field.Store.YES)); doc.add(new TextField("description", blogTitle.getDescription(), Field.Store.YES)); writer.updateDocument(new Term("id", "808"), doc); // 提交 writer.commit(); } catch (Exception e) { log.error("更新索引失敗", e); return "更新索引失敗"; } return "更新索引成功"; }
修改前搜索
然后將id=808的title
修改為0.SpringBoot不是目錄
,更新索引??梢钥吹綌祿炎兏?,但是分詞查詢,數據仍然查詢出來了。
3.5 刪除索引
@GetMapping("/deleteIndex") public String deleteIndex() { // 創(chuàng)建配置對象 IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer()); // 創(chuàng)建目錄對象 // 創(chuàng)建索引寫出工具 try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); IndexWriter writer = new IndexWriter(directory, conf)) { // 根據詞條進行刪除 writer.deleteDocuments(new Term("id", "808")); // 提交 writer.commit(); } catch (Exception e) { log.error("刪除索引失敗", e); return "刪除索引失敗"; } return "刪除索引成功"; }
只能刪除id=808的索引,然后再進行查詢,可以看到數據消失了。
4. 數據檢索
4.1 基礎搜索
最基礎的模糊搜索,功能不用文字解釋了,寫個sql的案例吧,很明顯就能懂。
當然走Lucene支持分詞檢索,計算得分展示等等,只為了容易懂,不杠…
select * from blog_title where title like ('%#{title}%')
/** * 簡單搜索 */ @RequestMapping("/searchText") public List<BlogTitle> searchText(String text) throws IOException, ParseException { Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 創(chuàng)建查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器 QueryParser parser = new QueryParser("title", new IKAnalyzer()); // 創(chuàng)建查詢對象 Query query = parser.parse(text); // 獲取前十條記錄 TopDocs topDocs = searcher.search(query, 10); // 獲取總條數 log.info("本次搜索共找到" + topDocs.totalHits + "條數據"); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; List<BlogTitle> list = new ArrayList<>(); for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docId = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docId); BlogTitle content = blogTitleMapper.selectById(doc.get("id")); list.add(content); } return list; }
GET http://localhost:8080/lucene/searchText?text=笑小楓
可以看到 title
中包含笑小楓
的數據都搜索出來了
4.2 一個關鍵詞,在多個字段里面搜索
關鍵詞在title
和description
兩個字段里面檢索,類似于下面的sql。
select * from blog_title where title like ('%#{searchPram}%') or description like ('%#{searchPram}%')
/** * 一個關鍵詞,在多個字段里面搜索 */ @RequestMapping("/searchTextMore") public List<BlogTitle> searchTextMore(String text) throws IOException, ParseException { String[] str = {"title", "description"}; Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 創(chuàng)建查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器 MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer()); // 創(chuàng)建查詢對象 Query query = parser.parse(text); // 獲取前十條記錄 TopDocs topDocs = searcher.search(query, 100); // 獲取總條數 log.info("本次搜索共找到" + topDocs.totalHits + "條數據"); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; List<BlogTitle> list = new ArrayList<>(); for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docId = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docId); BlogTitle content = blogTitleMapper.selectById(doc.get("id")); list.add(content); } return list; }
GET http://localhost:8080/lucene/searchTextMore?text=笑小楓
可以看到title
和description
中包含笑小楓
的數據都搜索出來了
4.3 搜索結果高亮顯示
這個功能基本必備吧,讓用戶明確知道搜索的匹配程度
/** * 搜索結果高亮顯示 */ @RequestMapping("/searchTextHighlighter") public List<BlogTitle> searchTextHighlighter(String text) throws IOException, ParseException, InvalidTokenOffsetsException { String[] str = {"title", "description"}; Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 創(chuàng)建查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器 MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer()); // 創(chuàng)建查詢對象 Query query = parser.parse(text); // 獲取前十條記錄 TopDocs topDocs = searcher.search(query, 100); // 獲取總條數 log.info("本次搜索共找到" + topDocs.totalHits + "條數據"); //高亮顯示 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); //高亮后的段落范圍在100字內 Fragmenter fragmenter = new SimpleFragmenter(100); highlighter.setTextFragmenter(fragmenter); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; List<BlogTitle> list = new ArrayList<>(); for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docId = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docId); BlogTitle content = blogTitleMapper.selectById(doc.get("id")); //處理高亮字段顯示 String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title")); if (title == null) { title = content.getTitle(); } // 因為創(chuàng)建索引的時候description設置的Field.Store.NO,所以這里doc沒有description數據,取不出來值,設為YES則可以,可以斷點看一下,直接設置content.getDescription()也可以高亮顯示 // String description = highlighter.getBestFragment(new IKAnalyzer(), "description", doc.get("description")); // if (description == null) { // description = content.getDescription(); // } // content.setDescription(description); content.setDescription(content.getDescription()); content.setTitle(title); list.add(content); } return list; }
GET http://localhost:8080/lucene/searchTextHighlighter?text=笑小楓
因為創(chuàng)建索引的時候description設置的Field.Store.NO,所以這里doc沒有description數據,取不出來值,故不做高亮,當然,從數據庫中查詢出來再做高亮也是可以的。
4.4 分頁檢索
不多說,你需要的我都整活,直接上代碼,分頁直接再程序中寫死了,正常需要傳分頁參數,返回分頁數據,總條數等,不利于演示,和普通分頁一樣,自己封裝吧
/** * 分頁搜索 */ @RequestMapping("/searchTextPage") public List<BlogTitle> searchTextPage(String text) throws IOException, ParseException, InvalidTokenOffsetsException { String[] str = {"title", "description"}; int page = 1; int pageSize = 5; Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 創(chuàng)建查詢解析器,兩個參數:默認要查詢的字段的名稱,分詞器 MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer()); // 創(chuàng)建查詢對象 Query query = parser.parse(text); // 分頁獲取數據 TopDocs topDocs = searchByPage(page, pageSize, searcher, query); // 獲取總條數 log.info("本次搜索共找到" + topDocs.totalHits + "條數據"); //高亮顯示 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); //高亮后的段落范圍在100字內 Fragmenter fragmenter = new SimpleFragmenter(100); highlighter.setTextFragmenter(fragmenter); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; List<BlogTitle> list = new ArrayList<>(); for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docId = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docId); BlogTitle content = blogTitleMapper.selectById(doc.get("id")); //處理高亮字段顯示 String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title")); if (title == null) { title = content.getTitle(); } String description = highlighter.getBestFragment(new IKAnalyzer(), "description", content.getDescription()); content.setDescription(description); content.setTitle(title); list.add(content); } return list; } private TopDocs searchByPage(int page, int perPage, IndexSearcher searcher, Query query) throws IOException { TopDocs result; if (query == null) { log.info(" Query is null return null "); return null; } ScoreDoc before = null; if (page != 1) { TopDocs docsBefore = searcher.search(query, (page - 1) * perPage); ScoreDoc[] scoreDocs = docsBefore.scoreDocs; if (scoreDocs.length > 0) { before = scoreDocs[scoreDocs.length - 1]; } } result = searcher.searchAfter(before, query, perPage); return result; }
GET http://localhost:8080/lucene/searchTextPage?text=笑小楓
第一頁數據:
第二頁數據:
手動修改int page = 2;
保證沒偷懶
4.5多個關鍵詞搜索
最起碼滿足你的日常使用吧。
/** * 多關鍵詞搜索 */ @GetMapping("/searchTextMoreParam") public List<BlogTitle> searchTextMoreParam(String text) throws IOException, ParseException, InvalidTokenOffsetsException { String[] str = {"title", "description"}; Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir")); // 索引讀取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); //多條件查詢構造 BooleanQuery.Builder builder = new BooleanQuery.Builder(); // 條件一 MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer()); // 創(chuàng)建查詢對象 Query query = parser.parse(text); builder.add(query, BooleanClause.Occur.MUST); // 條件二 // TermQuery不使用分析器所以建議匹配不分詞的Field域(StringField, )查詢,比如價格、分類ID號等。這里只能演示個ID了。。。 Query termQuery = new TermQuery(new Term("id", "839")); builder.add(termQuery, BooleanClause.Occur.MUST); // 獲取前十條記錄 TopDocs topDocs = searcher.search(builder.build(), 100); // 獲取總條數 log.info("本次搜索共找到" + topDocs.totalHits + "條數據"); //高亮顯示 SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>"); Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query)); //高亮后的段落范圍在100字內 Fragmenter fragmenter = new SimpleFragmenter(100); highlighter.setTextFragmenter(fragmenter); // 獲取得分文檔對象(ScoreDoc)數組.SocreDoc中包含:文檔的編號、文檔的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; List<BlogTitle> list = new ArrayList<>(); for (ScoreDoc scoreDoc : scoreDocs) { // 取出文檔編號 int docId = scoreDoc.doc; // 根據編號去找文檔 Document doc = reader.document(docId); BlogTitle content = blogTitleMapper.selectById(doc.get("id")); //處理高亮字段顯示 String title = highlighter.getBestFragment(new IKAnalyzer(), "title", doc.get("title")); if (title == null) { title = content.getTitle(); } String description = highlighter.getBestFragment(new IKAnalyzer(), "description", content.getDescription()); content.setDescription(description); content.setTitle(title); list.add(content); } return list; }
GET http://localhost:8080/lucene/searchTextMoreParam?text=mysql數據庫
5. IK擴展詞處理
什么是擴展詞呢?字面意思。
就如笑小楓
,我認為它是一個完整的詞匯,但是人家IK不認呀,怎么辦呢?
還有就是的
,和
,了
這些分詞檢索沒有太大意義的詞,我們可以過濾掉,不參與檢索。
不說廢話,怎么做呢?看圖~
添加上圖文件即可,生不生效,看高亮就很明顯,下文演示。
說說坑哈
坑一:注意打包后有沒有文件,如果沒有打進去的話,就會不生效
坑二:設置后,需要重新創(chuàng)建索引,不然可能會查不到數據
注意這個名字不能錯IKAnalyzer.cfg.xml
,放在resources
目錄下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> <properties> <comment>IKAnalyzer擴展配置</comment> <!--用戶的擴展字典 --> <entry key="ext_dict">extend.dic</entry> <!--用戶擴展停止詞字典 --> <entry key="ext_stopwords">stop.dic</entry> </properties>
extend.dic
對應上面文件中的名字(名字可以自定義,同步IKAnalyzer.cfg.xml
修改)和路徑,輸入多個回車即可
笑小楓系列 笑小楓 按步照搬
stop.dic
對應上面文件中的名字(名字可以自定義)和路徑,輸入多個回車即可
的 好 了
設置前:
設置后:
6. 項目源碼
本文到此就結束了,如果幫助到你了,幫忙點個贊
本文源碼:https://github.com/hack-feng/maple-product/tree/main/maple-lucene
以上就是SpringBoot整合Lucene實現全文檢索的詳細步驟的詳細內容,更多關于SpringBoot Lucene全文檢索的資料請關注腳本之家其它相關文章!
相關文章
Spring Boot Web應用開發(fā) CORS 跨域請求支持
本篇文章主要介紹了Spring Boot Web應用開發(fā) CORS 跨域請求支持,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Java中BigInteger與BigDecimal類用法總結
在Java中有兩個用于大數字運算的類,分別是java.math.BigInteger類 和 java.math.BigDecimal類,這兩個類都可以用于高精度計算,BigInteger類是針對整型大數字的處理類,而BigDecimal類是針對大小數的處理類,接下來帶大家來學習一下,在Java中如何處理大數字2023-05-05Java中Buffer緩沖區(qū)的ByteBuffer類詳解
這篇文章主要介紹了Java中Buffer緩沖區(qū)的ByteBuffer類詳解,ByteBuffer類是Java NIO庫中的一個重要類,用于處理字節(jié)數據,它提供了一種靈活的方式來讀取、寫入和操作字節(jié)數據,ByteBuffer類是一個抽象類,可以通過靜態(tài)方法創(chuàng)建不同類型的ByteBuffer對象,需要的朋友可以參考下2023-10-10