最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot整合Lucene實(shí)現(xiàn)全文檢索的詳細(xì)步驟

 更新時(shí)間:2024年03月12日 10:36:04   作者:笑小楓  
全文搜索(Full-Text?Search)是指對(duì)大規(guī)模存儲(chǔ)在計(jì)算機(jī)系統(tǒng)中的文本數(shù)據(jù)進(jìn)行檢索和匹配的技術(shù),它允許用戶(hù)輸入關(guān)鍵字,然后從海量的文本數(shù)據(jù)中快速找到相關(guān)的信息,本文介紹了SpringBoot整合Lucene實(shí)現(xiàn)全文檢索的詳細(xì)步驟,需要的朋友可以參考下

1. 項(xiàng)目背景

本文的出現(xiàn)是我的個(gè)人網(wǎng)站搭建的過(guò)程中產(chǎn)生的,作為一個(gè)技術(shù)博客為主的網(wǎng)站,Mysql的搜索已經(jīng)滿(mǎn)足不了我的野心了,于是,我便瞄上了全文檢索。最初,是打算直接使用比較熟悉的ES,但是考慮到部署ES額外的服務(wù)器資源開(kāi)銷(xiāo),最后選擇了Lucene,搭配IK分詞器,直接在項(xiàng)目中整合。

2. 什么是Lucene

看看官網(wǎng)上的介紹吧~

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.

看不懂,翻譯過(guò)來(lái)就是:

Apache Lucene™是一個(gè)完全用Java編寫(xiě)的高性能、全功能的搜索引擎庫(kù)。它是一種幾乎適用于任何需要結(jié)構(gòu)化搜索、全文搜索、切面、跨高維向量的最近鄰搜索、拼寫(xiě)糾正或查詢(xún)建議的應(yīng)用程序的技術(shù)。Apache Lucene是一個(gè)免費(fèi)下載的開(kāi)源項(xiàng)目。

沒(méi)錯(cuò),它就是我們需要的全文搜索引擎,接下來(lái)讓我們一起看看怎么在SpringBoot項(xiàng)目中集成使用它吧。

3. 引入依賴(lài),配置索引

3.1 引入Lucene依賴(lài)和分詞器依賴(lài)

先看看需要的依賴(lài)吧。

算了,還是先說(shuō)說(shuō)我的需求吧,算了,沒(méi)有需求,具體參考百度搜索框吧~反正就是那樣

直接上依賴(lài)吧,默認(rèn)分詞器對(duì)中文不友好。這里使用IK分詞器

        <!-- Lucene核心庫(kù) -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>7.6.0</version>
        </dependency>

        <!-- Lucene的查詢(xún)解析器 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>7.6.0</version>
        </dependency>

        <!-- Lucene的默認(rèn)分詞器庫(kù) -->
        <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版本沖突,會(huì)報(bào)以下錯(cuò)誤。

解決方案放在源碼中了,這里不展開(kāi)了。使用com.janeluo:ikanalyzer:2012_u6版本,把com.maple.lucene.util.MyIKAnalyzerMyIKTokenizer的注釋放開(kāi)就行。

image-20231214102750202

3.2 表結(jié)構(gòu)和數(shù)據(jù)準(zhǔn)備

準(zhǔn)備表結(jié)構(gòu),這里是簡(jiǎn)化過(guò)的表結(jié)構(gòu),只提供演示效果。

CREATE TABLE `blog_title` (
	`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
	`title` VARCHAR(255) NOT NULL COMMENT '標(biāo)題',
	`description` VARCHAR(255) NULL DEFAULT NULL COMMENT '描述',
	PRIMARY KEY (`id`) USING BTREE
)
COMMENT='博客標(biāo)題' COLLATE='utf8_general_ci' ENGINE=InnoDB;

準(zhǔn)備測(cè)試數(shù)據(jù):

INSERT INTO `blog_title` (`id`, `title`, `description`) VALUES 
(808, '0.SpringBoot目錄', 'https://xiaoxiaofeng.com'),
(809, '1.SpringBoot項(xiàng)目創(chuàng)建', '大家好,我是笑小楓,跟我一起玩轉(zhuǎn)SpringBoot項(xiàng)目吧,本文講一下如何搭建SpringBoot項(xiàng)目。'),
(810, '10.SpringBoot處理請(qǐng)求跨域問(wèn)題', 'CORS全稱(chēng)Cross-Origin Resource Sharing,意為跨域資源共享。當(dāng)一個(gè)資源去訪(fǎng)問(wèn)另一個(gè)不同域名或者同域名不同端口的資源時(shí),就會(huì)發(fā)出跨域請(qǐng)求。如果此時(shí)另一個(gè)資源不允許其進(jìn)行跨域資源訪(fǎng)問(wèn),那么訪(fǎng)問(wèn)就會(huì)遇到跨域問(wèn)題??缬蛑傅氖怯捎跒g覽器的安全性限制,不允許前端頁(yè)面訪(fǎng)問(wèn)協(xié)議不同、域名不同、端口號(hào)不同的http接口。'),
(811, '11.SpringBoot接口日志信息統(tǒng)一記錄', '為什么要記錄接口日志?\n至于為什么,詳細(xì)看到這里的小伙伴心里都有一個(gè)答案吧,我這里簡(jiǎn)單列一下常用的場(chǎng)景吧用戶(hù)登錄記錄統(tǒng)計(jì)、重要增刪改操作留痕、需要統(tǒng)計(jì)用戶(hù)的訪(fǎng)問(wèn)次數(shù)、接口調(diào)用情況統(tǒng)計(jì)、線(xiàn)上問(wèn)題排查、等等等...既然有這么多使用場(chǎng)景,那我們?cè)撛趺刺幚?,總不能一條一條的去記錄吧面試是不是老是被問(wèn)Spring的Aop的使用場(chǎng)景,那這個(gè)典型的場(chǎng)景就來(lái)了,我們可以使用Spring的Aop,完美的實(shí)現(xiàn)這個(gè)功能,接下來(lái)上代碼??'),
(812, '12.SpringBoot導(dǎo)入Excel', '在java處理excel方便從簡(jiǎn)單的實(shí)現(xiàn)功能到自己封裝工具類(lèi),一路走了好多,阿里的easyExcel對(duì)POI的封裝更加精簡(jiǎn)這里介紹一下簡(jiǎn)單使用。'),
(813, '13.SpringBoot導(dǎo)出Excel', '在java處理excel方便從簡(jiǎn)單的實(shí)現(xiàn)功能到自己封裝工具類(lèi),一路走了好多,阿里的easyExcel對(duì)POI的封裝更加精簡(jiǎn)這里介紹一下簡(jiǎn)單使用。'),
(814, '14.SpringBoot發(fā)送郵件', '本文主要介紹了使用SpringBoot發(fā)送郵件,主要包含如何獲取發(fā)送郵件的授權(quán)碼,這里以QQ郵箱為例,然后介紹了功能如何實(shí)現(xiàn),包括通過(guò)模板發(fā)送郵件,發(fā)送帶圖片的郵件,發(fā)送帶附件的郵件,發(fā)送帶有多個(gè)附件的郵件。'),
(815, '15.SpringBoot根據(jù)模板生成Word', '本文主要講了SpringBoot基于模板的形式生成word的功能實(shí)現(xiàn),感興趣或有類(lèi)似功能需求的小伙伴可以看一下,包括word模板制作,功能代碼實(shí)現(xiàn),支持導(dǎo)出圖片、表格等功能。'),
(816, '16.SpringBoot生成PDF', '本文主要介紹了在SpringBoot項(xiàng)目下,通過(guò)代碼和操作步驟,詳細(xì)的介紹了如何操作PDF。希望可以幫助到準(zhǔn)備通過(guò)JAVA操作PDF的你。\n本文涉及pdf操作,如下:\nPDF模板制作、 基于PDF模板生成,并支持下載、自定義中文字體、完全基于代碼生成,并保存到指定目錄、合并PDF,并保存到指定目錄、合并PDF,并支持下載\n'),
(817, '17.SpringBoot文件上傳下載', '在java開(kāi)發(fā)中文件的上傳、下載、刪除功能肯定是很常見(jiàn)的,本文主要基于上傳圖片或文件到指定的位置展開(kāi),通過(guò)詳細(xì)的代碼和工具類(lèi),講述java如何實(shí)現(xiàn)文件的上傳、下載、刪除。'),
(818, '18.SpringBoot中的Properties配置', 'springboot在使用過(guò)程中,我們有很多配置,比如mysql配置、redis配置、mybatis-plus、調(diào)用第三方的接口配置等等...\n\n我們現(xiàn)在都是放在一個(gè)大而全的配置里面的,如果我們想根據(jù)功能分為不同的配置文件管理,讓配置更加清晰,應(yīng)該怎么做呢?'),
(819, '19.使用Docker部署最佳實(shí)踐', '使用Docker部署最佳實(shí)踐'),
(820, '2.SpringBoot配置基于swagger2的knife4j接口文檔', 'SpringBoot項(xiàng)目如果前后端分離,怎么把寫(xiě)好了的接口返回給前端的小伙伴呢,試試這款基于Swagger2的knife4j吧,簡(jiǎn)直好用到爆!'),
(821, '3.SpringBoot集成Mybatis Plus', '本文主要介紹了SpringBoot集成mysql數(shù)據(jù)庫(kù)、集成Mybatis Plus框架;通過(guò)一個(gè)簡(jiǎn)單的例子演示了一下使用Mybatis Plus進(jìn)行數(shù)據(jù)插入和查詢(xún);使用Knife4j進(jìn)行接口調(diào)試;集成阿里巴巴Druid數(shù)據(jù)連接池;通過(guò)Druid頁(yè)面進(jìn)行執(zhí)行sql查詢(xún)、分析。'),
(822, '4.SpringBoot返回統(tǒng)一結(jié)果包裝', '前后端分離的時(shí)代,如果沒(méi)有統(tǒng)一的返回格式,給前端的結(jié)果各式各樣,估計(jì)前端的小伙伴就要罵娘了。  \n我們想對(duì)自定義異常拋出指定的狀態(tài)碼排查錯(cuò)誤,對(duì)系統(tǒng)的不可預(yù)知的異常拋出友好一點(diǎn)的異常信息。  \n我們想讓接口統(tǒng)一返回一些額外的數(shù)據(jù),例如接口執(zhí)行的時(shí)間等等。  那就進(jìn)來(lái)一起康康吧~......'),
(823, '5.SpringBoot返回統(tǒng)一異常處理', '如果程序拋異常了,我們是否也可以返回統(tǒng)一的格式呢?\n答案是,當(dāng)然可以的,不光可以?huà)伋鑫覀兿胍母袷?,還可以對(duì)指定的異常類(lèi)型進(jìn)行特殊處理\n例如使用@Validated對(duì)入?yún)⑿r?yàn)的異常,我們自定義的異常等等...'),
(824, '6.SpringBoot日志打印Logback詳解', 'Logback 旨在作為流行的 log4j 項(xiàng)目的繼承者,是SpringBoot內(nèi)置的日志處理框架,spring-boot-starter其中包含了spring-boot-starter-logging,該依賴(lài)內(nèi)容就是 Spring Boot 默認(rèn)的日志框架 logback。這里給大家介紹一下在SpraingBoot中Logback的配置。'),
(825, '7.SpringBoot控制臺(tái)自定義banner', '熬夜整理完logback相關(guān)的內(nèi)容,突然發(fā)現(xiàn)我們的《笑小楓系列-玩轉(zhuǎn)SpringBoot》已經(jīng)6篇文章了,我們的配套程序居然沒(méi)有一個(gè)屬于自己的log,這簡(jiǎn)直說(shuō)不過(guò)去了,我這處女座的小暴脾氣,趕緊整一個(gè),于是便有了此文。好了,接下來(lái)言歸正傳,畢竟本文也是屬于我們系列的一份子嘛,不能落下'),
(826, '8.SpringBoot集成Redis', 'SpringBoot中怎么使用Redis做緩存機(jī)制呢?本文為大家揭開(kāi)Redis的面紗,內(nèi)容偏基礎(chǔ),但詳細(xì)。本文核心:SpringBoot繼承redis、SpringBoot常用的redis操作演示、監(jiān)聽(tīng)Redis的key過(guò)期機(jī)制。'),
(827, '9.SpringBoot用戶(hù)登錄攔截器', '本文主要介紹了SpringBoot實(shí)現(xiàn)登錄功能,使用JWT+Redis進(jìn)行功能實(shí)現(xiàn),從最基礎(chǔ)的建表開(kāi)始,詳細(xì)的介紹了功能的實(shí)現(xiàn)。學(xué)習(xí)完本文,你將掌握登錄功能的核心技能。'),
(832, '【笑小楓的按步照搬系列】JDK8下載安裝配置', '本文主要講解了JDK8在windows環(huán)境下的下載、安裝、已經(jīng)環(huán)境變量的配置,參照本文,你只需要按步照搬,便可快速的安裝好JAVA環(huán)境。'),
(833, '【笑小楓的按步照搬系列】Maven環(huán)境配置', '本文主要介紹了maven的安裝配置,包括配置本地倉(cāng)庫(kù),配置阿里鏡像等。安裝maven環(huán)境之前要先安裝java jdk環(huán)境(沒(méi)有安裝java環(huán)境的可以先去看安裝JAVA環(huán)境的教程)Maven 3.3+ require JDK 1.7 及以上。'),
(834, '【笑小楓的按步照搬系列】Node.js安裝', 'Node.js安裝'),
(835, '【笑小楓的按步照搬系列】Redis可視化工具-RedisInsight', 'RedisInsight是Redis官方出品的可視化管理工具,可用于設(shè)計(jì)、開(kāi)發(fā)、優(yōu)化你的Redis應(yīng)用。支持深色和淺色兩種主題,界面非常炫酷!可支持String、Hash、Set、List、JSON等多種數(shù)據(jù)類(lèi)型的管理,同時(shí)支持遠(yuǎn)程使用CLI功能,功能非常強(qiáng)大!'),
(836, '【笑小楓的按步照搬系列】Redis多系統(tǒng)安裝(Windows、Linux、Ubuntu)', 'Redis(Remote Dictionary Server ),即遠(yuǎn)程字典服務(wù),是一個(gè)開(kāi)源的使用ANSI C語(yǔ)言編寫(xiě)、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日志型、Key-Value數(shù)據(jù)庫(kù),并提供多種語(yǔ)言的API。本文主要講述了Redis如何安裝。'),
(837, '【笑小楓的按步照搬系列】開(kāi)源的服務(wù)器遠(yuǎn)程工具-FinalShell', '之前一直使用 xshell + ftp 組合的方式來(lái)部署項(xiàng)目,后來(lái)發(fā)現(xiàn)了FinalShell 這款軟件,瞬間就愛(ài)上了。FinalShell 相當(dāng)于 xshell + ftp 的組合,即:FinalShell = xshell + ftp ;FinalShell 只用一個(gè)程序,將xshell 、ftp同屏顯示,既可以輸入命令,也可以傳輸數(shù)據(jù),還能以樹(shù)的形式展示文件路徑。'),
(840, '【笑小楓的按步照搬系列】本地安裝Mysql數(shù)據(jù)庫(kù)', '本文主要介紹了在windows環(huán)境下如何下載安裝mysql8+版本,你只需要按步照搬就可以完美解決你安裝軟件的困擾。本文主要包括mysql的下載、安裝、配置my.ini文件、修改初始化密碼等。'),
(841, '【笑小楓的按步照搬系列】版本控制工具git安裝過(guò)程詳解', 'Git 是個(gè)免費(fèi)的開(kāi)源分布式版本控制系統(tǒng),下載地址為git-scm.com 或者 gitforwindows.org,本文介紹  Git-2.35.1.2-64-bit.exe 版本的安裝方法,需要的小伙伴可以看一看。');

對(duì)數(shù)據(jù)庫(kù)的操作使用的Mybatis Plus,這里演示比較簡(jiǎn)單,只是單純的取數(shù)據(jù),不貼詳細(xì)代碼了,需要的去源碼里面獲取。不想連數(shù)據(jù)庫(kù)可以直接用個(gè)List模擬掉,簡(jiǎn)單的貼個(gè)對(duì)象吧。

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

/**
 * <p>
 * blog標(biāo)題
 * </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)建索引

這里直接從數(shù)據(jù)庫(kù)查詢(xún)所有數(shù)據(jù),然后創(chuàng)建索引了,只為演示,實(shí)際使用中根據(jù)數(shù)據(jù)量大小,業(yè)務(wù)需要哪些字段,是否需要回表查詢(xún)等等考慮生產(chǎn)方案,釣無(wú)定法,技術(shù)多彩

直接上代碼了,索引建在d:\\indexDir目錄下,實(shí)際使用該封裝封裝,該放配置放配置哈。這里為了演示效果好(方便你們copy),集中都放在這里了。 注釋比較詳細(xì),不單獨(dú)介紹功能了。

如果新增數(shù)據(jù)追加的話(huà),使用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)建文檔對(duì)象
            Document document = new Document();

            // StringField: 這個(gè) Field 用來(lái)構(gòu)建一個(gè)字符串Field,不分析,會(huì)索引,F(xiàn)ield.Store控制存儲(chǔ)
            // LongPoint、IntPoint 等類(lèi)型存儲(chǔ)數(shù)值類(lèi)型的數(shù)據(jù)。會(huì)分析,會(huì)索引,不存儲(chǔ),如果想存儲(chǔ)數(shù)據(jù)還需要使用 StoredField
            // StoredField: 這個(gè) Field 用來(lái)構(gòu)建不同類(lèi)型,不分析,不索引,會(huì)存儲(chǔ)
            // TextField: 如果是一個(gè)Reader, 會(huì)分析,會(huì)索引,,F(xiàn)ield.Store控制存儲(chǔ)
            document.add(new StringField("id", String.valueOf(blogTitle.getId()), Field.Store.YES));
            // Field.Store.YES, 將原始字段值存儲(chǔ)在索引中。這對(duì)于短文本很有用,比如文檔的標(biāo)題,它應(yīng)該與結(jié)果一起顯示。
            // 值以其原始形式存儲(chǔ),即在存儲(chǔ)之前沒(méi)有使用任何分析器。
            document.add(new TextField("title", blogTitle.getTitle(), Field.Store.YES));
            // Field.Store.NO,可以索引,分詞,不將字段值存儲(chǔ)在索引中。
            // 個(gè)人理解:說(shuō)白了就是為了省空間,如果回表查詢(xún),其實(shí)無(wú)所謂,如果不回表查詢(xún),需要展示就要保存,設(shè)為YES,無(wú)需展示,設(shè)為NO即可。
            document.add(new TextField("description", blogTitle.getDescription(), Field.Store.NO));
            docs.add(document);
        }

        // 引入IK分詞器,如果需要解決上面版本沖突報(bào)錯(cuò)的問(wèn),使用`new MyIKAnalyzer()`即可
        Analyzer analyzer = new IKAnalyzer();
        // 索引寫(xiě)出工具的配置對(duì)象
        IndexWriterConfig conf = new IndexWriterConfig(analyzer);
        // 設(shè)置打開(kāi)方式:OpenMode.APPEND 會(huì)在索引庫(kù)的基礎(chǔ)上追加新索引。OpenMode.CREATE會(huì)先清空原來(lái)數(shù)據(jù),再提交新的索引
        conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

        // 索引目錄類(lèi),指定索引在硬盤(pán)中的位置,我的設(shè)置為D盤(pán)的indexDir文件夾
        // 創(chuàng)建索引的寫(xiě)出工具類(lèi)。參數(shù):索引的目錄和配置信息
        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目錄下會(huì)出現(xiàn)索引文件,如下圖

image-20231214140419526

3.4 修改索引

數(shù)據(jù)變更時(shí),索引應(yīng)該怎么變更呢?該如何怎么設(shè)計(jì)呢?

  • 在程序中數(shù)據(jù)變更的時(shí)候,更新索引,但是對(duì)業(yè)務(wù)的侵入性比較大。新增、修改、刪除時(shí)都要多一套操作Lucene的接口。
  • 監(jiān)聽(tīng)數(shù)據(jù)庫(kù)數(shù)據(jù)變更,然后更新索引,引入額外中間件,復(fù)雜度變高。

有舍有得吧,看權(quán)衡點(diǎn)在哪了,大家有什么好的方案可以留言喲。

    @GetMapping("/updateIndex")
    public String update() {
        // 創(chuàng)建配置對(duì)象
        IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());
        // 創(chuàng)建目錄對(duì)象
        // 創(chuàng)建索引寫(xiě)出工具
        try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));
             IndexWriter writer = new IndexWriter(directory, conf)) {
            // 獲取更新的數(shù)據(jù),這里只是演示
            BlogTitle blogTitle = blogTitleMapper.selectById("808");

            // 創(chuàng)建新的文檔數(shù)據(jù)
            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 "更新索引成功";
    }

修改前搜索

image-20231214140651268

然后將id=808的title修改為0.SpringBoot不是目錄,更新索引??梢钥吹綌?shù)據(jù)已變更,但是分詞查詢(xún),數(shù)據(jù)仍然查詢(xún)出來(lái)了。

image-20231214140828502

3.5 刪除索引

    @GetMapping("/deleteIndex")
    public String deleteIndex() {
        // 創(chuàng)建配置對(duì)象
        IndexWriterConfig conf = new IndexWriterConfig(new IKAnalyzer());
        // 創(chuàng)建目錄對(duì)象
        // 創(chuàng)建索引寫(xiě)出工具
        try (Directory directory = FSDirectory.open(FileSystems.getDefault().getPath("d:\\indexDir"));
             IndexWriter writer = new IndexWriter(directory, conf)) {
            // 根據(jù)詞條進(jìn)行刪除
            writer.deleteDocuments(new Term("id", "808"));
            // 提交
            writer.commit();
        } catch (Exception e) {
            log.error("刪除索引失敗", e);
            return "刪除索引失敗";
        }
        return "刪除索引成功";
    }

只能刪除id=808的索引,然后再進(jìn)行查詢(xún),可以看到數(shù)據(jù)消失了。

image-20231214141047751

4. 數(shù)據(jù)檢索

4.1 基礎(chǔ)搜索

最基礎(chǔ)的模糊搜索,功能不用文字解釋了,寫(xiě)個(gè)sql的案例吧,很明顯就能懂。

當(dāng)然走Lucene支持分詞檢索,計(jì)算得分展示等等,只為了容易懂,不杠…

select * from blog_title where title like ('%#{title}%')
  	/**
      * 簡(jiǎn)單搜索
      */
     @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)建查詢(xún)解析器,兩個(gè)參數(shù):默認(rèn)要查詢(xún)的字段的名稱(chēng),分詞器
         QueryParser parser = new QueryParser("title", new IKAnalyzer());
         // 創(chuàng)建查詢(xún)對(duì)象
         Query query = parser.parse(text);
         // 獲取前十條記錄
         TopDocs topDocs = searcher.search(query, 10);
         // 獲取總條數(shù)
         log.info("本次搜索共找到" + topDocs.totalHits + "條數(shù)據(jù)");
         // 獲取得分文檔對(duì)象(ScoreDoc)數(shù)組.SocreDoc中包含:文檔的編號(hào)、文檔的得分
         ScoreDoc[] scoreDocs = topDocs.scoreDocs;
         List<BlogTitle> list = new ArrayList<>();
         for (ScoreDoc scoreDoc : scoreDocs) {
             // 取出文檔編號(hào)
             int docId = scoreDoc.doc;
             // 根據(jù)編號(hào)去找文檔
             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 中包含笑小楓的數(shù)據(jù)都搜索出來(lái)了

image-20231214141900547

4.2 一個(gè)關(guān)鍵詞,在多個(gè)字段里面搜索

關(guān)鍵詞在titledescription兩個(gè)字段里面檢索,類(lèi)似于下面的sql。

select * from blog_title where title like ('%#{searchPram}%') or description like ('%#{searchPram}%')
	/**
     * 一個(gè)關(guān)鍵詞,在多個(gè)字段里面搜索
     */
    @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)建查詢(xún)解析器,兩個(gè)參數(shù):默認(rèn)要查詢(xún)的字段的名稱(chēng),分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());
        // 創(chuàng)建查詢(xún)對(duì)象
        Query query = parser.parse(text);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(query, 100);
        // 獲取總條數(shù)
        log.info("本次搜索共找到" + topDocs.totalHits + "條數(shù)據(jù)");
        // 獲取得分文檔對(duì)象(ScoreDoc)數(shù)組.SocreDoc中包含:文檔的編號(hào)、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BlogTitle> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編號(hào)
            int docId = scoreDoc.doc;
            // 根據(jù)編號(hào)去找文檔
            Document doc = reader.document(docId);
            BlogTitle content = blogTitleMapper.selectById(doc.get("id"));
            list.add(content);
        }
        return list;
    }
GET http://localhost:8080/lucene/searchTextMore?text=笑小楓

可以看到titledescription中包含笑小楓的數(shù)據(jù)都搜索出來(lái)了

image-20231214142223945

4.3 搜索結(jié)果高亮顯示

這個(gè)功能基本必備吧,讓用戶(hù)明確知道搜索的匹配程度

    /**
     * 搜索結(jié)果高亮顯示
     */
    @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)建查詢(xún)解析器,兩個(gè)參數(shù):默認(rèn)要查詢(xún)的字段的名稱(chēng),分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());
        // 創(chuàng)建查詢(xún)對(duì)象
        Query query = parser.parse(text);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(query, 100);
        // 獲取總條數(shù)
        log.info("本次搜索共找到" + topDocs.totalHits + "條數(shù)據(jù)");

        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        //高亮后的段落范圍在100字內(nèi)
        Fragmenter fragmenter = new SimpleFragmenter(100);
        highlighter.setTextFragmenter(fragmenter);

        // 獲取得分文檔對(duì)象(ScoreDoc)數(shù)組.SocreDoc中包含:文檔的編號(hào)、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BlogTitle> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編號(hào)
            int docId = scoreDoc.doc;
            // 根據(jù)編號(hào)去找文檔
            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();
            }
            // 因?yàn)閯?chuàng)建索引的時(shí)候description設(shè)置的Field.Store.NO,所以這里doc沒(méi)有description數(shù)據(jù),取不出來(lái)值,設(shè)為YES則可以,可以斷點(diǎn)看一下,直接設(shè)置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=笑小楓

因?yàn)閯?chuàng)建索引的時(shí)候description設(shè)置的Field.Store.NO,所以這里doc沒(méi)有description數(shù)據(jù),取不出來(lái)值,故不做高亮,當(dāng)然,從數(shù)據(jù)庫(kù)中查詢(xún)出來(lái)再做高亮也是可以的。

image-20231214143607619

4.4 分頁(yè)檢索

不多說(shuō),你需要的我都整活,直接上代碼,分頁(yè)直接再程序中寫(xiě)死了,正常需要傳分頁(yè)參數(shù),返回分頁(yè)數(shù)據(jù),總條數(shù)等,不利于演示,和普通分頁(yè)一樣,自己封裝吧

    /**
     * 分頁(yè)搜索
     */
    @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)建查詢(xún)解析器,兩個(gè)參數(shù):默認(rèn)要查詢(xún)的字段的名稱(chēng),分詞器
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());
        // 創(chuàng)建查詢(xún)對(duì)象
        Query query = parser.parse(text);
        // 分頁(yè)獲取數(shù)據(jù)
        TopDocs topDocs = searchByPage(page, pageSize, searcher, query);
        // 獲取總條數(shù)
        log.info("本次搜索共找到" + topDocs.totalHits + "條數(shù)據(jù)");

        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        //高亮后的段落范圍在100字內(nèi)
        Fragmenter fragmenter = new SimpleFragmenter(100);
        highlighter.setTextFragmenter(fragmenter);

        // 獲取得分文檔對(duì)象(ScoreDoc)數(shù)組.SocreDoc中包含:文檔的編號(hào)、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BlogTitle> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編號(hào)
            int docId = scoreDoc.doc;
            // 根據(jù)編號(hào)去找文檔
            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=笑小楓

第一頁(yè)數(shù)據(jù):

image-20231214144040209

第二頁(yè)數(shù)據(jù):

手動(dòng)修改int page = 2;保證沒(méi)偷懶

image-20231214144126075

4.5多個(gè)關(guān)鍵詞搜索

最起碼滿(mǎn)足你的日常使用吧。

    /**
     * 多關(guān)鍵詞搜索
     */
    @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);

        //多條件查詢(xún)構(gòu)造
        BooleanQuery.Builder builder = new BooleanQuery.Builder();

        // 條件一
        MultiFieldQueryParser parser = new MultiFieldQueryParser(str, new IKAnalyzer());
        // 創(chuàng)建查詢(xún)對(duì)象
        Query query = parser.parse(text);
        builder.add(query, BooleanClause.Occur.MUST);
        // 條件二
        // TermQuery不使用分析器所以建議匹配不分詞的Field域(StringField, )查詢(xún),比如價(jià)格、分類(lèi)ID號(hào)等。這里只能演示個(gè)ID了。。。
        Query termQuery = new TermQuery(new Term("id", "839"));
        builder.add(termQuery, BooleanClause.Occur.MUST);
        // 獲取前十條記錄
        TopDocs topDocs = searcher.search(builder.build(), 100);
        // 獲取總條數(shù)
        log.info("本次搜索共找到" + topDocs.totalHits + "條數(shù)據(jù)");
        //高亮顯示
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<span style='color:red'>", "</span>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, new QueryScorer(query));
        //高亮后的段落范圍在100字內(nèi)
        Fragmenter fragmenter = new SimpleFragmenter(100);
        highlighter.setTextFragmenter(fragmenter);

        // 獲取得分文檔對(duì)象(ScoreDoc)數(shù)組.SocreDoc中包含:文檔的編號(hào)、文檔的得分
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        List<BlogTitle> list = new ArrayList<>();
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 取出文檔編號(hào)
            int docId = scoreDoc.doc;
            // 根據(jù)編號(hào)去找文檔
            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數(shù)據(jù)庫(kù)

image-20231214151540036

5. IK擴(kuò)展詞處理

什么是擴(kuò)展詞呢?字面意思。

就如笑小楓,我認(rèn)為它是一個(gè)完整的詞匯,但是人家IK不認(rèn)呀,怎么辦呢?

還有就是,,這些分詞檢索沒(méi)有太大意義的詞,我們可以過(guò)濾掉,不參與檢索。

不說(shuō)廢話(huà),怎么做呢?看圖~

image-20231214135027228

添加上圖文件即可,生不生效,看高亮就很明顯,下文演示。

說(shuō)說(shuō)坑哈

坑一:注意打包后有沒(méi)有文件,如果沒(méi)有打進(jìn)去的話(huà),就會(huì)不生效

坑二:設(shè)置后,需要重新創(chuàng)建索引,不然可能會(huì)查不到數(shù)據(jù)

注意這個(gè)名字不能錯(cuò)IKAnalyzer.cfg.xml,放在resources目錄下

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IKAnalyzer擴(kuò)展配置</comment>
    <!--用戶(hù)的擴(kuò)展字典 -->
    <entry key="ext_dict">extend.dic</entry>
    <!--用戶(hù)擴(kuò)展停止詞字典 -->
    <entry key="ext_stopwords">stop.dic</entry>
</properties>

extend.dic對(duì)應(yīng)上面文件中的名字(名字可以自定義,同步IKAnalyzer.cfg.xml修改)和路徑,輸入多個(gè)回車(chē)即可

笑小楓系列
笑小楓
按步照搬

stop.dic對(duì)應(yīng)上面文件中的名字(名字可以自定義)和路徑,輸入多個(gè)回車(chē)即可

的
好
了

設(shè)置前:

image-20231214144040209

設(shè)置后:

image-20231214152353465

6. 項(xiàng)目源碼

本文到此就結(jié)束了,如果幫助到你了,幫忙點(diǎn)個(gè)贊

本文源碼:https://github.com/hack-feng/maple-product/tree/main/maple-lucene

以上就是SpringBoot整合Lucene實(shí)現(xiàn)全文檢索的詳細(xì)步驟的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Lucene全文檢索的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java模板方法模式定義算法框架

    Java模板方法模式定義算法框架

    Java模板方法模式是一種行為型設(shè)計(jì)模式,它定義了一個(gè)算法框架,由抽象父類(lèi)定義算法的基本結(jié)構(gòu),具體實(shí)現(xiàn)細(xì)節(jié)由子類(lèi)來(lái)實(shí)現(xiàn),從而實(shí)現(xiàn)代碼復(fù)用和擴(kuò)展性
    2023-05-05
  • Spring Data JPA的作用和用法小結(jié)

    Spring Data JPA的作用和用法小結(jié)

    Spring Data JPA是Spring框架的一個(gè)模塊,它提供了一種數(shù)據(jù)訪(fǎng)問(wèn)抽象,允許以一種聲明式和簡(jiǎn)潔的方式來(lái)處理數(shù)據(jù)庫(kù)操作,本文主要介紹了Spring Data JPA的作用和用法小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • 解析Java定時(shí)任務(wù)的選型及改造問(wèn)題

    解析Java定時(shí)任務(wù)的選型及改造問(wèn)題

    這篇文章主要介紹了Java定時(shí)任務(wù)的選型及改造問(wèn)題,本文給大家提到了Java主流三大定時(shí)任務(wù)框架優(yōu)缺點(diǎn),通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • SSH框架網(wǎng)上商城項(xiàng)目第2戰(zhàn)之基本增刪查改、Service和Action的抽取

    SSH框架網(wǎng)上商城項(xiàng)目第2戰(zhàn)之基本增刪查改、Service和Action的抽取

    SSH框架網(wǎng)上商城項(xiàng)目第2戰(zhàn)之基本增刪查改、Service和Action的抽取,感興趣的小伙伴們可以參考一下
    2016-05-05
  • Java在Excel中創(chuàng)建透視表方法解析

    Java在Excel中創(chuàng)建透視表方法解析

    這篇文章主要介紹了Java在Excel中創(chuàng)建透視表方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 基于SpringBoot使用Tika實(shí)現(xiàn)文檔解析

    基于SpringBoot使用Tika實(shí)現(xiàn)文檔解析

    Apache?Tika是開(kāi)源內(nèi)容分析工具,支持多格式文本提取與元數(shù)據(jù)解析,具備語(yǔ)言檢測(cè)和MIME類(lèi)型識(shí)別功能,適用于搜索引擎、數(shù)據(jù)分析等場(chǎng)景,在SpringBoot中集成需注意性能及配置問(wèn)題,支持流式處理和自定義擴(kuò)展,下面介紹SpringBoot使用Tika實(shí)現(xiàn)文檔解析,感興趣的朋友一起看看吧
    2025-07-07
  • Java深入講解static操作符

    Java深入講解static操作符

    static關(guān)鍵字基本概念我們可以一句話(huà)來(lái)概括:方便在沒(méi)有創(chuàng)建對(duì)象的情況下來(lái)進(jìn)行調(diào)用。也就是說(shuō):被static關(guān)鍵字修飾的不需要?jiǎng)?chuàng)建對(duì)象去調(diào)用,直接根據(jù)類(lèi)名就可以去訪(fǎng)問(wèn),讓我們來(lái)了解一下你可能還不知道情況
    2022-07-07
  • Java Kafka 消費(fèi)積壓監(jiān)控的示例代碼

    Java Kafka 消費(fèi)積壓監(jiān)控的示例代碼

    這篇文章主要介紹了Java Kafka 消費(fèi)積壓監(jiān)控,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-07-07
  • Spring中@PathVariable注解的簡(jiǎn)單使用

    Spring中@PathVariable注解的簡(jiǎn)單使用

    這篇文章主要介紹了Spring中@PathVariable注解的簡(jiǎn)單使用,@PathVariable 是 Spring Framework 中的注解之一,用于處理 RESTful Web 服務(wù)中的 URL 路徑參數(shù),它的作用是將 URL 中的路徑變量綁定到方法的參數(shù)上,需要的朋友可以參考下
    2024-01-01
  • Java框架解說(shuō)之BIO NIO AIO不同IO模型演進(jìn)之路

    Java框架解說(shuō)之BIO NIO AIO不同IO模型演進(jìn)之路

    網(wǎng)上很多IO資料,對(duì)新手來(lái)說(shuō),越看越暈。根據(jù)自己的理解,總結(jié)對(duì)比了一下BIO、NIO、AIO,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10

最新評(píng)論

松潘县| 潜江市| 利川市| 靖安县| 新乡市| 吴川市| 新田县| 都匀市| 子长县| 习水县| 东兴市| 凌云县| 婺源县| 通道| 永城市| 西平县| 威远县| 如皋市| 昭通市| 平安县| 班玛县| 盐池县| 阳泉市| 涞水县| 新蔡县| 卢龙县| 沙雅县| 长泰县| 肃宁县| 美姑县| 贡山| 吉木乃县| 高雄市| 大余县| 申扎县| 昌宁县| 周宁县| 灵川县| 金昌市| 成都市| 光泽县|