1. 程式人生 > >SSM(二)Lucene全文檢索

SSM(二)Lucene全文檢索

前言

大家平時肯定都有用過全文檢索工具,最常用的百度谷歌就是其中的典型。如果自己能夠做一個那是不是想想就逼格滿滿呢。Apache就為我們提供了這樣一個框架,以下就是在實際開發中加入Lucene的一個小Demo。

獲取Maven依賴

首先看一下實際執行的效果圖:


這個專案是基於之前使用IDEA搭建的SSM的基礎上進行增加的,建議小白先看下一我。上一篇部落格,以及共享在Github上的原始碼
以下是Lucene所需要的依賴:

<!--加入lucene-->
        <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-core -->
<dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>${lucene.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
<dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>${lucene.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-common -->
<dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>${lucene.version}</version> </dependency> <!--lucene中文分詞--> <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-analyzers-smartcn --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-smartcn</artifactId> <version>${lucene.version}</version> </dependency> <!--lucene高亮--> <!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-highlighter --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>${lucene.version}</version> </dependency>

具體的用途我都寫有註釋。
在IDEA中修改了Pom.xml檔案之後只需要點選如圖所示的按鈕即可重新獲取依賴:

編寫Lucene工具類

這個工具類中的具體程式碼我就不單獨提出來說了,每個關鍵的地方我都寫有註釋,不清楚的再討論。

package com.crossoverJie.lucene;

import com.crossoverJie.pojo.User;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;

import java.io.StringReader;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import com.crossoverJie.util.*;

/**
 * 部落格索引類
 * @author Administrator
 *
 */
public class LuceneIndex {

    private Directory dir=null;

    /**
     * 獲取IndexWriter例項
     * @return
     * @throws Exception
     */
    private IndexWriter getWriter()throws Exception{
        /**
         * 生成的索引我放在了C盤,可以根據自己的需要放在具體位置
         */
        dir= FSDirectory.open(Paths.get("C://lucene"));
        SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();
        IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
        IndexWriter writer=new IndexWriter(dir, iwc);
        return writer;
    }

    /**
     * 新增部落格索引
     * @param user
     */
    public void addIndex(User user)throws Exception{
        IndexWriter writer=getWriter();
        Document doc=new Document();
        doc.add(new StringField("id",String.valueOf(user.getUserId()), Field.Store.YES));
        /**
         * yes是會將資料存進索引,如果查詢結果中需要將記錄顯示出來就要存進去,如果查詢結果
         * 只是顯示標題之類的就可以不用存,而且內容過長不建議存進去
         * 使用TextField類是可以用於查詢的。
         */
        doc.add(new TextField("username", user.getUsername(), Field.Store.YES));
        doc.add(new TextField("description",user.getDescription(), Field.Store.YES));
        writer.addDocument(doc);
        writer.close();
    }

    /**
     * 更新部落格索引
     * @param user
     * @throws Exception
     */
    public void updateIndex(User user)throws Exception{
        IndexWriter writer=getWriter();
        Document doc=new Document();
        doc.add(new StringField("id",String.valueOf(user.getUserId()), Field.Store.YES));
        doc.add(new TextField("username", user.getUsername(), Field.Store.YES));
        doc.add(new TextField("description",user.getDescription(), Field.Store.YES));
        writer.updateDocument(new Term("id", String.valueOf(user.getUserId())), doc);
        writer.close();
    }

    /**
     * 刪除指定部落格的索引
     * @param userId
     * @throws Exception
     */
    public void deleteIndex(String userId)throws Exception{
        IndexWriter writer=getWriter();
        writer.deleteDocuments(new Term("id", userId));
        writer.forceMergeDeletes(); // 強制刪除
        writer.commit();
        writer.close();
    }

    /**
     * 查詢使用者
     * @param q 查詢關鍵字
     * @return
     * @throws Exception
     */
    public List<User> searchBlog(String q)throws Exception{
        /**
         * 注意的是查詢索引的位置得是存放索引的位置,不然會找不到。
         */
        dir= FSDirectory.open(Paths.get("C://lucene"));
        IndexReader reader = DirectoryReader.open(dir);
        IndexSearcher is=new IndexSearcher(reader);
        BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
        SmartChineseAnalyzer analyzer=new SmartChineseAnalyzer();
        /**
         * username和description就是我們需要進行查詢的兩個欄位
         * 同時在存放索引的時候要使用TextField類進行存放。
         */
        QueryParser parser=new QueryParser("username",analyzer);
        Query query=parser.parse(q);
        QueryParser parser2=new QueryParser("description",analyzer);
        Query query2=parser2.parse(q);
        booleanQuery.add(query, BooleanClause.Occur.SHOULD);
        booleanQuery.add(query2, BooleanClause.Occur.SHOULD);
        TopDocs hits=is.search(booleanQuery.build(), 100);
        QueryScorer scorer=new QueryScorer(query);
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
        /**
         * 這裡可以根據自己的需要來自定義查詢關鍵字高亮時的樣式。
         */
        SimpleHTMLFormatter simpleHTMLFormatter=new SimpleHTMLFormatter("<b><font color='red'>","</font></b>");
        Highlighter highlighter=new Highlighter(simpleHTMLFormatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        List<User> userList=new LinkedList<User>();
        for(ScoreDoc scoreDoc:hits.scoreDocs){
            Document doc=is.doc(scoreDoc.doc);
            User user=new User();
            user.setUserId(Integer.parseInt(doc.get(("id"))));
            user.setDescription(doc.get(("description")));
            String username=doc.get("username");
            String description=doc.get("description");
            if(username!=null){
                TokenStream tokenStream = analyzer.tokenStream("username", new StringReader(username));
                String husername=highlighter.getBestFragment(tokenStream, username);
                if(StringUtil.isEmpty(husername)){
                    user.setUsername(username);
                }else{
                    user.setUsername(husername);
                }
            }
            if(description!=null){
                TokenStream tokenStream = analyzer.tokenStream("description", new StringReader(description));
                String hContent=highlighter.getBestFragment(tokenStream, description);
                if(StringUtil.isEmpty(hContent)){
                    if(description.length()<=200){
                        user.setDescription(description);
                    }else{
                        user.setDescription(description.substring(0, 200));
                    }
                }else{
                    user.setDescription(hContent);
                }
            }
            userList.add(user);
        }
        return userList;
    }
}

查詢Controller的編寫

接下來是查詢Controller:

    @RequestMapping("/q")
    public String search(@RequestParam(value = "q", required = false,defaultValue = "") String q,
                         @RequestParam(value = "page", required = false, defaultValue = "1") String page,
                         Model model,
                         HttpServletRequest request) throws Exception {
        LuceneIndex luceneIndex = new LuceneIndex() ;
        List<User> userList = luceneIndex.searchBlog(q);
        /**
         * 關於查詢之後的分頁我採用的是每次分頁發起的請求都是將所有的資料查詢出來,
         * 具體是第幾頁再擷取對應頁數的資料,典型的拿空間換時間的做法,如果各位有什麼
         * 高招歡迎受教。
         */
        Integer toIndex = userList.size() >= Integer.parseInt(page) * 5 ? Integer.parseInt(page) * 5 : userList.size();
        List<User> newList = userList.subList((Integer.parseInt(page) - 1) * 5, toIndex);
        model.addAttribute("userList",newList) ;
        String s = this.genUpAndDownPageCode(Integer.parseInt(page), userList.size(), q, 5, request.getServletContext().
                getContextPath());
        model.addAttribute("pageHtml",s) ;
        model.addAttribute("q",q) ;
        model.addAttribute("resultTotal",userList.size()) ;
        model.addAttribute("pageTitle","搜尋關鍵字'" + q + "'結果頁面") ;

        return "queryResult";
    }

其中有用到一個genUpAndDownPageCode()方法來生成分頁的Html程式碼,如下:

    /**
     * 查詢之後的分頁
     * @param page
     * @param totalNum
     * @param q
     * @param pageSize
     * @param projectContext
     * @return
     */
    private String genUpAndDownPageCode(int page,Integer totalNum,String q,Integer pageSize,String projectContext){
        long totalPage=totalNum%pageSize==0?totalNum/pageSize:totalNum/pageSize+1;
        StringBuffer pageCode=new StringBuffer();
        if(totalPage==0){
            return "";
        }else{
            pageCode.append("<nav>");
            pageCode.append("<ul class='pager' >");
            if(page>1){
                pageCode.append("<li><a href='"+projectContext+"/q?page="+(page-1)+"&q="+q+"'>上一頁</a></li>");
            }else{
                pageCode.append("<li class='disabled'><a href='#'>上一頁</a></li>");
            }
            if(page<totalPage){
                pageCode.append("<li><a href='"+projectContext+"/q?page="+(page+1)+"&q="+q+"'>下一頁</a></li>");
            }else{
                pageCode.append("<li class='disabled'><a href='#'>下一頁</a></li>");
            }
            pageCode.append("</ul>");
            pageCode.append("</nav>");
        }
        return pageCode.toString();
    }

程式碼比較簡單,就是根據的頁數、總頁數來生成分頁程式碼,對了我前端採用的是現在流行的Bootstrap,這個有不會的可以去他官網看看,比較簡單易上手。接下來只需要編寫顯示介面就大功告成了。

顯示介面

我只貼關鍵程式碼,具體的可以去Github上檢視。

<c:choose>
                    <c:when test="${userList.size()==0 }">
                        <div align="center" style="padding-top: 20px"><font color="red">${q}</font>未查詢到結果,請換個關鍵字試試!</div>
                    </c:when>
                    <c:otherwise>
                        <div align="center" style="padding-top: 20px">
                            查詢<font color="red">${q}</font>關鍵字,約${resultTotal}條記錄!
                        </div>
                        <c:forEach var="u" items="${userList }" varStatus="status">
                            <div class="panel-heading ">

                                <div class="row">
                                    <div class="col-md-6">
                                        <div class="row">
                                            <div class="col-md-12">
                                                <b>
                                                    <a href="<%=path %>/user/showUser/${u.userId}">${u.username}</a>
                                                </b>
                                                <br/>
                                                    ${u.description}
                                            </div>
                                        </div>
                                    </div>
                                    <div class="col-md-4 col-md-offset-2">
                                        <p class="text-muted text-right">
                                                ${u.password}
                                        </p>
                                    </div>
                                </div>
                            </div>
                            <div class="panel-footer">
                                <p class="text-right">
                            <span class="label label-default">
                            <span class="glyphicon glyphicon-comment" aria-hidden="true"></span>
                             ${u.password}
                            </span>
                                </p>
                            </div>
                        </c:forEach>
                    </c:otherwise>
                </c:choose>

利用JSTL標籤即可將資料迴圈展示出來,關鍵字就不需要單獨做處理了,在後臺查詢的時候已經做了修改了。

總結

關於全文檢索的框架不止Lucene還有solr,具體誰好有什麼區別我也不太清楚,準備下來花點時間研究下。哦對了,最近又有點想做Android開發了,感覺做點東西能夠實實在在的摸得到逼格確實要高些(現在主要在做後端開發),感興趣的朋友可以關注下。哦對了,直接執行我程式碼的朋友要下注意:
- 首先要將資料庫倒到自己的MySQL上
- 之後在首次執行的時候需要點選重新生成索引按鈕生成一遍索引之後才能進行搜尋,因為現在的資料是直接存到資料庫中的,並沒有在新增的時候就增加索引,在實際開發的時候需要在新增資料那裡再生成一份索引,就直接呼叫LuceneIndex類中的addIndex方法傳入實體即可,再做更新、刪除操作的時候也同樣需要對索引做操作。

相關推薦

SSM()Lucene全文檢索

前言 大家平時肯定都有用過全文檢索工具,最常用的百度谷歌就是其中的典型。如果自己能夠做一個那是不是想想就逼格滿滿呢。Apache就為我們提供了這樣一個框架,以下就是在實際開發中加入Lucene的一個小Demo。 獲取Maven依賴 首先看一

Lucene全文檢索引擎

getname 通過 nal dem 檢索 數據庫 project cep 關閉 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSch

Lucene 全文檢索入門

sdi 執行 option getter itl .get png 廣泛 fig 博客地址:http://www.moonxy.com 一、前言 Lucene 是 apache 軟件基金會的一個子項目,由 Doug Cutting 開發,是一個開放源代碼的全文檢索引擎工具包

Lucene全文檢索之倒排索引實現原理、API解析【2018.11】

》 官網 http://lucene.apache.org/ 下載地址:https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/java/7.5.0/ 》 Lucene的全文檢索是指什麼: 程式掃描文件

Lucene全文檢索框架

Lucene全文檢索框架 1、什麼時Lucene? 是一個全文搜尋框架,而不是應用產品,他只是一種工具讓你能實現某些產品,並不像www.baidu.com拿來就能用 是apache組織的一個用java實現的全文搜尋引擎的開源專案 2、Luncen的工作方式? 提供的服務實際包含兩部分:一入一

Lucene全文檢索學習

花了一段時間學習lucene今天有時間把所學的寫下來,網上有很多文章但大部分都是2.X和3.X版本的(當前最新版本4.9),希望這篇文章對自己和初學者有所幫助。    學習目錄 (1)什麼是lucene (2)lucene常用類詳解 (3)lucene簡單例項 (4)luce

Lucene全文檢索隨筆

一,什麼是全文檢索 全文檢索是計算機程式通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置。當用戶查詢時根據建立的索引查詢,類似於通過字典的檢索字表查字的過程。 全文檢索(Full-Text Retrieval)以文字作為檢索物件,找出含有指定詞彙的文字。全面、準確和快速是

Lucene全文檢索入門使用

一、 什麼是全文檢索 全文檢索是計算機程式通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置。當用戶查詢時根據建立的索引查詢,類似於通過字典的檢索字表查字的過程 全文檢索(Full-Text Retrieval)以文字作為檢索物件,找出含有指定詞彙的文字。全面、準確和快速是

Lucene&全文檢索

目錄結構: 1.全文檢索 2.Lucene入門 3.Lucene進階 全文檢索 一, 生活中的搜尋: 1.Windows系統中的有搜尋功能:開啟“我的電腦”,按“F3”就可以使用查詢的功能,查詢指定的檔案或資料夾。搜尋的範圍是整個電腦中的檔案資源。 2.Eclips

Lucene全文檢索--實戰篇

一、配置開發環境 版本:lucene4.10.3 Jdk要求:1.7以上 IDE:Eclipse jar包:  Lucene包:    lucene-core-4.10.3.jar    lucene-analyzers-

Lucene全文檢索--理論篇

【案例】 實現一個檔案的搜尋功能,通過關鍵字搜尋檔案,凡是檔名或檔案內容包括關鍵字的檔案都需要找出來。還可以根據中文詞語進行查詢,並且需要支援多個條件查詢。 本案例中的原始內容就是磁碟上的檔案,如下圖:   1. 全文檢索(Full-text Search) &n

Apache Lucene 全文檢索詳解及開發示例

講解之前,先來分享一些資料   首先呢,學習任何一門新的亦或是舊的開源技術,百度其中一二是最簡單的辦法,先了解其中的大概,思想等等。這裡就貢獻一個講解很到位的ppt。已經被我轉成了PDF,便於蒐藏。   其次,關於第一次程式設計初探,建議還是檢視官方資料。百度到的資料

Lucene全文檢索工具包學習筆記總結

Lucene—-全文檢索的工具包 隸屬於apache(solr也是屬於apache,solr底層的實現是Lucene) 一、資料的分類: 結構化資料 具有固定型別和長度的資料 比如:資料庫(mysql/oracl)中的資料,元資料(windows中的檔案) 非結構化資料

lucene全文檢索與資料庫檢索的區別

1.效能 資料庫:like檢索(會把表中資料進行一行一行的掃描,)效能慢 Lucene檢索:先把資料那過來建立檢索,然後在根據建立的索引進行查詢,這樣的話我們需要多維護一份索引表。多一個建立索引的過程

Lucene: 全文檢索的基本原理

一、總論 Lucene是一個高效的,基於Java的全文檢索庫。 所以在瞭解Lucene之前要費一番工夫瞭解一下全文檢索。 那麼什麼叫做全文檢索呢?這要從我們生活中的資料說起。 我們生活中的資料總體分為兩種:結構化資料和非結構化資料。 結構化資料:指具有固定格式或有限長度的資料,如資料庫,元資料等。非結構化

Lucene】Apache Lucene全文檢索引擎架構之搜尋功能

  上一節主要總結了一下Lucene是如何構建索引的,這一節簡單總結一下Lucene中的搜尋功能。主要分為幾個部分,對特定項的搜尋;查詢表示式QueryParser的使用;指定數字範圍內搜尋;指定字串開

java Lucene全文檢索優化方法

一. .索引優化背景 很多網站都有自己的搜尋引擎,比如百度,搜狗等等,而他們每天新增的索引量可想而知多麼龐大,所以為了能提升使用者的搜尋響應速度,好的優化方案必不可少;當然對於一些網站的站內搜尋也很有

Lucene全文檢索引擎工具包使用方法總結

Lucene是apache軟體基金會4 jakarta專案組的一個子專案,是一個開放原始碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文字分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟體

Lucene】Apache Lucene全文檢索引擎架構之入門實戰

  Lucene是一套用於全文檢索和搜尋的開源程式庫,由Apache軟體基金會支援和提供。Lucene提供了一個簡單卻強大的應用程式介面,能夠做全文索引和搜尋。在Java開發環境裡Lucene是一個成熟的免費開源工具。就其本身而言,Lucene是當前以

使用Lucene進行全文檢索()---得到有效的內容(轉載)

在使用lucene對相關內容進行索引時,會遇到各種格式的內容,例如html,pdf,word等等,那麼我們如何從這麼文件中得到我們需要的內容哪?例如html的內容,一般我們不需要對html標籤建立索引,因為那不是我們需要搜尋的內容.這個時候,我們就需要從html內容中解析