Solr開發

Solr 是一種可供企業使用的、基於 Lucene 的搜索服務器,它支持層面搜索、命中醒目顯示和多種輸出格式。在這篇文章中,將介紹 Solr 並展示如何輕鬆地將其表現優異的全文本搜索功能加入到 Web 應用程序中。

開發環境:

System:Windows

WebBrowser:IE6+、Firefox3+

JDK:1.6+

JavaEE Server:tomcat5.0.2.8、tomcat6

IDE:eclipse、MyEclipse 8

 

開發依賴庫:

JavaEE 5、solr 3.4

 

個人博客:

http://hoojo.cnblogs.com

http://blog.csdn.net/IBM_hoojo

email: [email protected]

 

一、配置和安裝solr

1、 首先去apache官方網站下載solr,下載地址

http://labs.renren.com/apache-mirror//lucene/solr/3.4.0/

目前最新的是3.4的版本

 

2、 下載後解壓目錄如下

clip_image002

client是一個ruby實現的示例,這個我們暫時不管

contrib有一些功能模塊是需要的jar包

dist是打包發佈好的工程war包

docs是幫助文檔

example是示例,裏面有打包部署好的solr工程示例和servlet容器jetty。如果你沒有tomcat可以直接使用Jetty服務器部署你的solr示例。

 

3、 發佈、部署solr示例

A、 利用自帶的Jetty服務器

首先在dos命令中進入到下載好的solr解壓的目錄apache-solr-3.4.0的example目錄

cd E:\JAR\solr\apache-solr-3.4.0\example

然後利用java命令,啓動jetty服務器。Java –jar start.jar

clip_image004

啓動Jetty成功後,如果沒有看到錯誤消息,你可以看到端口信息。

clip_image006

如果你的端口衝突了,你可以到解壓的solr示例包的example/etc的jetty.xml中,修改端口port信息。

<Set name="port">

<SystemProperty name="jetty.port" default="8983"/>

</Set>

 

B、 利用tomcat發佈solr示例

將下載的solr解壓後,進入apache-solr-3.4.0\dist目錄,將裏面的solr.war放到D:\tomcat-6.0.28\webapps目錄下,啓動tomcat會自動解壓。(當然,你也可以手動解壓放到wabapps目錄下)

當然你也可以設置context指向你的solr工程,在D:\tomcat-6.0.28\conf\Catalina\localhost目錄加入solr.xml配置,配置如下:

<Context docBase="D:\solr.war" debug="0" crossContext="true" >
    <Environment name="solr/home" type="java.lang.String" value="D:\solr" override="true" />
</Context>

上面的2步都是一樣的,這樣還沒有完。啓動後你可能會看到如下錯誤:

clip_image008

我們需要將一些配置和index庫文件也放到解壓好的solr工程下。我們到解壓的apache-solr-3.4.0\example\solr目錄下,將裏面的conf和data目錄copy到剛纔我們部署的D:\tomcat-6.0.28\webapps\solr工程目錄下。或是copy到你的solr.xml中的context指定的路徑下工程目錄中。

重啓tomcat就ok了。

 

4、 這個時候你就可以訪問http://localhost:8983/solr/admin/你就可以看到如下界面:

clip_image010

在Query String中輸入solr,點擊Search就可以查詢到相應的結果,結果以xml形式返回。當然你也可以設置返回數據類型爲json。

<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
  <int name="status">0</int>
  <int name="QTime">0</int>
  <lst name="params">
    <str name="indent">on</str>
    <str name="start">0</str>
    <str name="q">solr</str>
    <str name="version">2.2</str>
    <str name="rows">10</str>
  </lst>
</lst>
<result name="response" numFound="1" start="0">
  <doc>
    <arr name="cat"><str>software</str><str>search</str></arr>
    <arr name="features"><str>Advanced Full-Text Search Capabilities using Lucene</str><str>Optimized for High Volume Web Traffic</str><str>Standards Based Open Interfaces - XML and HTTP</str>
      <str>Comprehensive HTML Administration Interfaces</str><str>Scalability - Efficient Replication to other Solr Search Servers</str><str>Flexible and Adaptable with XML configuration and Schema</str><str>Good unicode support: h¨¦llo (hello with an accent over the e)</str></arr>
    <str name="id">SOLR1000</str>
    <bool name="inStock">true</bool>
    <date name="incubationdate_dt">2006-01-17T00:00:00Z</date>
    <str name="manu">Apache Software Foundation</str>
    <str name="name">Solr, the Enterprise Search Server</str>
    <int name="popularity">10</int>
    <float name="price">0.0</float>
  </doc>
</result>
</response>
 

 

二、Solr理論

1、 solr基礎

因爲 Solr 包裝並擴展了 Lucene,所以它們使用很多相同的術語。更重要的是,Solr 創建的索引與 Lucene 搜索引擎庫完全兼容。通過對 Solr 進行適當的配置,某些情況下可能需要進行編碼,Solr 可以閱讀和使用構建到其他 Lucene 應用程序中的索引。

在 Solr 和 Lucene 中,使用一個或多個 Document 來構建索引。Document 包括一個或多個 Field。Field 包括名稱、內容以及告訴 Solr 如何處理內容的元數據。例如,Field 可以包含字符串、數字、布爾值或者日期,也可以包含你想添加的任何類型,只需用在solr的配置文件中進行相應的配置即可。Field 可以使用大量的選項來描述,這些選項告訴 Solr 在索引和搜索期間如何處理內容。現在,查看一下表 1 中列出的重要屬性的子集:

屬性名稱

描述

Indexed

Indexed Field 可以進行搜索和排序。你還可以在 indexed Field 上運行 Solr 分析過程,此過程可修改內容以改進或更改結果。

Stored

stored Field 內容保存在索引中。這對於檢索和醒目顯示內容很有用,但對於實際搜索則不是必需的。例如,很多應用程序存儲指向內容位置的指針而不是存儲實際的文件內容。

 

2、 solr索引操作

在 Solr 中,通過向部署在 servlet 容器中的 Solr Web 應用程序發送 HTTP 請求來啓動索引和搜索。Solr 接受請求,確定要使用的適當 SolrRequestHandler,然後處理請求。通過 HTTP 以同樣的方式返回響應。默認配置返回 Solr 的標準 XML 響應。你也可以配置 Solr 的備用響應格式,如json、csv格式的文本。

索引就是接受輸入元數據(數據格式在schema.xml中進行配置)並將它們傳遞給 Solr,從而在 HTTP Post XML 消息中進行索引的過程。你可以向 Solr 索引 servlet 傳遞四個不同的索引請求:

add/update 允許您向 Solr 添加文檔或更新文檔。直到提交後才能搜索到這些添加和更新。

commit 告訴 Solr,應該使上次提交以來所做的所有更改都可以搜索到。

optimize 重構 Lucene 的文件以改進搜索性能。索引完成後執行一下優化通常比較好。如果更新比較頻繁,則應該在使用率較低的時候安排優化。一個索引無需優化也可以正常地運行。優化是一個耗時較多的過程。

delete 可以通過 id 或查詢來指定。按 id 刪除將刪除具有指定 id 的文檔;按查詢刪除將刪除查詢返回的所有文檔。

Lucene中操作索引也有這幾個步驟,但是沒有更新。Lucene更新是先刪除,然後添加索引。因爲更新索引在一定情況下,效率沒有先刪除後添加的效率好。

 

3、 搜索

添加文檔後,就可以搜索這些文檔了。Solr 接受 HTTP GET 和 HTTP POST 查詢消息。收到的查詢由相應的 SolrRequestHandler 進行處理。

solr查詢參數描述:

參數

描述

示例

q

Solr 中用來搜索的查詢。有關該語法的完整描述,請參閱 參考資料。可以通過追加一個分號和已索引且未進行斷詞的字段(下面會進行解釋)的名稱來包含排序信息。默認的排序是 score desc,指按記分降序排序。

q=myField:Java AND otherField:developerWorks; date asc

此查詢搜索指定的兩個字段,並根據一個日期字段對結果進行排序。

start

將初始偏移量指定到結果集中。可用於對結果進行分頁。默認值爲 0。

start=15

返回從第 15 個結果開始的結果。

rows

返回文檔的最大數目。默認值爲 10。

rows=25,返回25個結果集

fq

提供一個可選的篩選器查詢。查詢結果被限制爲僅搜索篩選器查詢返回的結果。篩選過的查詢由 Solr 進行緩存。它們對提高複雜查詢的速度非常有用。

任何可以用 q 參數傳遞的有效查詢,排序信息除外。

hl

當 hl=true 時,在查詢響應中醒目顯示片段。默認爲 false。參看醒目顯示參數(見 參考資料)。

hl=true

fl

作爲逗號分隔的列表指定文檔結果中應返回的 Field 集。默認爲 “*”,指所有的字段。“score” 指還應返回記分。

*,score

sort

排序,對查詢結果進行排序,參考

sort=date asc,price desc

 

4、 solr模式

上面有提到schema.xml這個配置,這個配置可以在你下載solr包的安裝解壓目錄的apache-solr-3.4.0\example\solr\conf中找到,它就是solr模式關聯的文件。打開這個配置文件,你會發現有詳細的註釋。

模式組織主要分爲三個重要配置

types 部分是一些常見的可重用定義,定義了 Solr(和 Lucene)如何處理 Field。也就是添加到索引中的xml文件屬性中的類型,如int、text、date等

fileds是你添加到索引文件中出現的屬性名稱,而聲明類型就需要用到上面的types

其他配置有

uniqueKey 唯一鍵,這裏配置的是上面出現的fileds,一般是id、url等不重複的。在更新、刪除的時候可以用到。

defaultSearchField默認搜索屬性,如q=solr就是默認的搜索那個字段

solrQueryParser查詢轉換模式,是並且還是或者(and/or)

schema配置類型

<fieldType name="text" class="solr.TextField" positionIncrementGap="100">
    <analyzer type="index">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.StopFilterFactory" ignoreCase="true"
            words="stopwords.txt" />
        <filter class="solr.WordDelimiterFilterFactory"
            generateWordParts="1" generateNumberParts="1" catenateWords="1"
            catenateNumbers="1" catenateAll="0" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt" />
        <filter class="solr.RemoveDuplicatesTokenFilterFactory" />
    </analyzer>
    <analyzer type="query">
        <tokenizer class="solr.WhitespaceTokenizerFactory" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt"
            ignoreCase="true" expand="true" />
        <filter class="solr.StopFilterFactory" ignoreCase="true"
            words="stopwords.txt" />
        <filter class="solr.WordDelimiterFilterFactory"
            generateWordParts="1" generateNumberParts="1" catenateWords="0"
            catenateNumbers="0" catenateAll="0" />
        <filter class="solr.LowerCaseFilterFactory" />
        <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt" />
        <filter class="solr.RemoveDuplicatesTokenFilterFactory" />
    </analyzer>
</fieldType>

上面就是一個type了,然後你在fields配置field的時候就可以用這個type。

 

首先,上面的fieldType的配置中有兩個analyzer,它是分詞器。主要把我們的數據進行分割成一個個的詞語。詞幹提取、停止詞刪除以及相似的操作都被應用於標記,然後才進行索引和搜索,導致使用相同類型的標記。

上面的應用程序的 Solr 的fieldType配置按以下步驟進行設置:

Ø 根據空白進行斷詞,然後刪除所有的公共詞(StopFilterFactory)

Ø 使用破折號處理特殊的大小寫、大小寫轉換等等。(WordDelimiterFilterFactory);將所有條目處理爲小寫(LowerCaseFilterFactory)

Ø 使用 Porter Stemming 算法進行詞幹提取(EnglishPorterFilterFactory)

Ø 刪除所有的副本(RemoveDuplicatesTokenFilterFactory)

Schema屬性、字段

<field name="id" type="string" indexed="true" stored="true"
    required="true" />
<field name="sku" type="text_en_splitting_tight" indexed="true"
    stored="true" omitNorms="true" />
<field name="name" type="text_general" indexed="true" stored="true" />
<field name="alphaNameSort" type="alphaOnlySort" indexed="true"
    stored="false" />
<field name="manu" type="text_general" indexed="true" stored="true"
    omitNorms="true" />
<field name="cat" type="string" indexed="true" stored="true"
    multiValued="true" />
<field name="features" type="text_general" indexed="true" stored="true"
    multiValued="true" />
<field name="includes" type="text_general" indexed="true" stored="true"
    termVectors="true" termPositions="true" termOffsets="true" />

屬性是在添加索引、查詢的時候必須的配置,如果你不加這些配置。是無法完成索引的創建的。

首先id屬性是未經分析的字符串類型,是可以索引、存儲的,並且是唯一的。

sku是一個經過分詞器分析出來的英文切割的類型字符,可以索引、存儲、不要存儲規範

multiValued 屬性是一個特殊的例子,指 Document 可以擁有一個相同名稱添加了多次的 Field。

omitNorms 屬性告訴 Solr(和 Lucene)不要存儲規範。

介紹一下字段聲明下方的 <dynamicField> 聲明。動態字段是一些特殊類型的字段,可以在任何時候將這些字段添加到任何文檔中,由字段聲明定義它們的屬性。動態字段和普通字段之間的關鍵區別在於前者不需要在 schema.xml 中提前聲明名稱。Solr 將名稱聲明中的 glob-like 模式應用到所有尚未聲明的引入的字段名稱,並根據其 <dynamicField> 聲明定義的語義來處理字段。例如,<dynamicField name="*_i" type="sint" indexed="true" stored="true"/> 指一個 myRating_i 字段被 Solr 處理爲 sint,儘管並未將其聲明爲字段。這種處理比較方便,例如,當需要用戶定義待搜索內容的時候。

 

5、 索引配置

Solr 性能因素,來了解與各種更改相關的性能權衡。

表 1 概括了可控制 Solr 索引處理的各種因素:

因素

描述

useCompoundFile

通過將很多 Lucene 內部文件整合到單一一個文件來減少使用中的文件的數量。這可有助於減少 Solr 使用的文件句柄數目,代價是降低了性能。除非是應用程序用完了文件句柄,否則 false 的默認值應該就已經足夠。

mergeFactor

決定低水平的 Lucene 段被合併的頻率。較小的值(最小爲 2)使用的內存較少但導致的索引時間也更慢。較大的值可使索引時間變快但會犧牲較多的內存。

maxBufferedDocs

在合併內存中文檔和創建新段之前,定義所需索引的最小文檔數。段 是用來存儲索引信息的 Lucene 文件。較大的值可使索引時間變快但會犧牲較多的內存。

maxMergeDocs

控制可由 Solr 合併的 Document 的最大數。較小的值 (< 10,000) 最適合於具有大量更新的應用程序。

maxFieldLength

對於給定的 Document,控制可添加到 Field 的最大條目數,進而截斷該文檔。如果文檔可能會很大,就需要增加這個數值。然而,若將這個值設置得過高會導致內存不足錯誤。

unlockOnStartup

unlockOnStartup 告知 Solr 忽略在多線程環境中用來保護索引的鎖定機制。在某些情況下,索引可能會由於不正確的關機或其他錯誤而一直處於鎖定,這就妨礙了添加和更新。將其設置爲 true 可以禁用啓動鎖定,進而允許進行添加和更新。

 

6、 查詢處理配置

<maxBooleanClauses> 標記定義了可組合在一起形成一個查詢的子句數量的上限。對於大多數應用程序而言,默認的 1024 就應該已經足夠;然而,如果應用程序大量使用了通配符或範圍查詢,增加這個限值將能避免當值超出時,拋出 TooManyClausesException。

若應用程序預期只會檢索 Document 上少數幾個 Field,那麼可以將 <enableLazyFieldLoading> 屬性設置爲 true。懶散加載的一個常見場景大都發生在應用程序返回和顯示一系列搜索結果的時候,用戶常常會單擊其中的一個來查看存儲在此索引中的原始文檔。初始的顯示常常只需要顯示很短的一段信息。若考慮到檢索大型 Document 的代價,除非必需,否則就應該避免加載整個文檔。

<query> 部分負責定義與在 Solr 中發生的事件相關的幾個選項。Searcher 的 Java 類來處理 Query 實例。要改進這一設計和顯著提高性能,把這些新的 Searcher 聯機以便爲現場用戶提供查詢服務之前,先對它們進行 “熱身”。<query> 部分中的 <listener> 選項定義 newSearcher 和 firstSearcher 事件,您可以使用這些事件來指定實例化新搜索程序或第一個搜索程序時應該執行哪些查詢。如果應用程序期望請求某些特定的查詢,那麼在創建新搜索程序或第一個搜索程序時就應該反註釋這些部分並執行適當的查詢。

solrconfig.xml 文件的剩餘部分,除 <admin> 之外,涵蓋了與 緩存、複製 和 擴展或定製 Solr 有關的項目。admin 部分讓您可以定製管理界面。有關配置 admin 節的更多信息,請參看solrconfig.xml 文件中的註釋。

 

7、 監視、記錄和統計數據

用於監視、記錄和統計數據的 Solr 管理選項

菜單名

URL

描述

Statistics

http://localhost:8080/solr/admin/stats.jsp

Statistics 管理頁提供了與 Solr 性能相關的很多有用的統計數據。這些數據包括:

關於何時加載索引以及索引中有多少文檔的信息。

關於用來服務查詢的 SolrRequestHandler 的有用信息。

涵蓋索引過程的數據,包括添加、刪除、提交等的數量。

緩存實現和 hit/miss/eviction 信息

Info

http://localhost:8080/solr/admin/registry.jsp

有關正在運行的 Solr 的版本以及在當前實現中進行查詢、更新和緩存所使用的類的詳細信息。此外,還包括文件存於 Solr subversion 存儲庫的何處的信息以及對該文件功能的一個簡要描述。

Distribution

http://localhost:8080/solr/admin/distributiondump.jsp

顯示與索引發布和複製有關的信息。更多信息,請參見 “發佈和複製” 一節。

Ping

http://localhost:8080/solr/admin/ping

向服務器發出 ping 請求,包括在 solrconfig.xml 文件的 admin 部分定義的請求。

Logging

http://localhost:8080/solr/admin/logging.jsp

讓您可以動態更改當前應用程序的日誌記錄等級。更改日誌記錄等級對於調試在執行過程中可能出現的問題非常有用。

properties

http: //localhost:8080/solr/admin/get-properties.jsp

顯示當前系統正在使用的所有 Java 系統屬性。Solr 支持通過命令行的系統屬性替換。有關實現此特性的更多信息,請參見 solrconfig.xml 文件。

Thread dump

http://localhost:8080/solr/admin/threaddump.jsp

thread dump 選項顯示了在 JVM 中運行的所有線程的堆棧跟蹤信息。

 

8、 智能緩存

智能緩存是讓 Solr 得以成爲引人矚目的搜索服務器的一個關鍵性能特徵。Solr 提供了四種不同的緩存類型,所有四種類型都可在 solrconfig.xml 的 <query> 部分中配置。solrconfig.xml 文件中所用的標記名列出了這些緩存類型:

緩存標記名

描述

能否自熱

filterCache

通過存儲一個匹配給定查詢的文檔 id 的無序集,過濾器讓 Solr 能夠有效提高查詢的性能。緩存這些過濾器意味着對 Solr 的重複調用可以導致結果集的快速查找。更常見的場景是緩存一個過濾器,然後再發起後續的精煉查詢,這種查詢能使用過濾器來限制要搜索的文檔數。

可以

queryResultCache

爲查詢、排序條件和所請求文檔的數量緩存文檔 id 的有序 集合。

可以

documentCache

緩存 Lucene Document,使用內部 Lucene 文檔 id(以便不與 Solr 惟一 id 相混淆)。由於 Lucene 的內部 Document id 可以因索引操作而更改,這種緩存不能自熱。

不可以

Named caches

命名緩存是用戶定義的緩存,可被 Solr 定製插件 所使用。

可以,

如果實現了 org.apache.solr.search.CacheRegenerator 的話。

每個緩存聲明都接受最多四個屬性:

class 是緩存實現的 Java 名。

size 是最大的條目數。

initialSize 是緩存的初始大小。

autoWarmCount 是取自舊緩存以預熱新緩存的條目數。如果條目很多,就意味着緩存的 hit 會更多,只不過需要花更長的預熱時間。

 

三、利用SolrJ操作solr API,完成index操作

使用SolrJ操作Solr會比利用httpClient來操作Solr要簡單。SolrJ是封裝了httpClient方法,來操作solr的API的。SolrJ底層還是通過使用httpClient中的方法來完成Solr的操作。

1、 首先,你需要添加如下jar包

clip_image012

其中apache-solr-solrj-3.4.0.jar、slf4j-api-1.6.1.jar可以在下載的apache-solr-3.4.0的壓縮包中的dist中能找到。

 

2、 其次,建立一個簡單的測試類,完成Server對象的相關方法的測試工作,代碼如下:

package com.hoo.test;
 
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.hoo.entity.Index;
 
/**
 * <b>function:</b> Server TestCase
 * @author hoojo
 * @createDate 2011-10-19 下午01:49:07
 * @file ServerTest.java
 * @package com.hoo.test
 * @project SolrExample
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class ServerTest {
    
    private SolrServer server;
    private CommonsHttpSolrServer httpServer;
    
    private static final String DEFAULT_URL = "http://localhost:8983/solr/";
    
    @Before
    public void init() {
        try {
            server = new CommonsHttpSolrServer(DEFAULT_URL);
            httpServer = new CommonsHttpSolrServer(DEFAULT_URL);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }
    
    @After
    public void destory() {
        server = null;
        httpServer = null;
        System.runFinalization();
        System.gc();
    }
    
    public final void fail(Object o) {
        System.out.println(o);
    }
    
    /**
     * <b>function:</b> 測試是否創建server對象成功
     * @author hoojo
     * @createDate 2011-10-21 上午09:48:18
     */
    @Test
    public void server() {
        fail(server);
        fail(httpServer);
    }
 
    /**
     * <b>function:</b> 根據query參數查詢索引
     * @author hoojo
     * @createDate 2011-10-21 上午10:06:39
     * @param query
     */
    public void query(String query) {
        SolrParams params = new SolrQuery(query);
        
        try {
            QueryResponse response = server.query(params);
            
            SolrDocumentList list = response.getResults();
            for (int i = 0; i < list.size(); i++) {
                fail(list.get(i));
            }
        } catch (SolrServerException e) {
            e.printStackTrace();
        } 
    }
}

測試運行server case方法,如果成功創建對象,那你就成功的鏈接到。

注意:在運行本方法之前,請啓動你的solr官方自動的項目。http://localhost:8983/solr/保證能夠成功訪問這個工程。因爲接下來的所有工作都是圍繞這個solr工程完成的。如果你現在還不知道,怎麼部署、發佈官方solr工程,請參考前面的具體章節。

 

3、 Server的有關配置選項參數,server是CommonsHttpSolrServer的實例

server.setSoTimeout(1000); // socket read timeout 
server.setConnectionTimeout(100); 
server.setDefaultMaxConnectionsPerHost(100); 
server.setMaxTotalConnections(100); 
server.setFollowRedirects(false); // defaults to false 
// allowCompression defaults to false. 
// Server side must support gzip or deflate for this to have any effect. 
server.setAllowCompression(true); 
server.setMaxRetries(1); // defaults to 0.  > 1 not recommended. 
 
//sorlr J 目前使用二進制的格式作爲默認的格式。對於solr1.2的用戶通過顯示的設置才能使用XML格式。
server.setParser(new XMLResponseParser());
 
//二進制流輸出格式
//server.setRequestWriter(new BinaryRequestWriter());

 

4、 利用SolrJ完成Index Document的添加操作

/**
 * <b>function:</b> 添加doc文檔
 * @author hoojo
 * @createDate 2011-10-21 上午09:49:10
 */
@Test
public void addDoc() {
    //創建doc文檔
     SolrInputDocument doc = new SolrInputDocument();
    doc.addField("id", 1);
    doc.addField("name", "Solr Input Document");
    doc.addField("manu", "this is SolrInputDocument content");
    
    try {
        //添加一個doc文檔
        UpdateResponse response = server.add(doc);
        fail(server.commit());//commit後才保存到索引庫
        fail(response);
        fail("query time:" + response.getQTime());
        fail("Elapsed Time:" + response.getElapsedTime());
        fail("status:" + response.getStatus());
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    query("name:solr");
}

在apache-solr-3.4.0\example\solr\conf目錄下的schema.xml中可以找到有關於field屬性的配置,schema.xml中的field就和上面Document文檔中的field(id、name、manu)對應。如果出現ERROR:unknown field 'xxxx'就表示你設置的這個field在schema.xml中不存在。如果一定要使用這個field,請你在schema.xml中進行filed元素的配置。具體請參考前面的章節。

注意:在schema.xml中配置了uniqueKey爲id,就表示id是唯一的。如果在添加Document的時候,id重複添加。那麼後面添加的相同id的doc會覆蓋前面的doc,類似於update更新操作,而不會出現重複的數據。

 

5、 利用SolrJ添加多個Document,即添加文檔集合

/**
 * <b>function:</b> 添加docs文檔集合
 * @author hoojo
 * @createDate 2011-10-21 上午09:55:01
 */
@Test
public void addDocs() {
    Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
    
    SolrInputDocument doc = new SolrInputDocument();
    doc.addField("id", 2);
    doc.addField("name", "Solr Input Documents 1");
    doc.addField("manu", "this is SolrInputDocuments 1 content");
    
    docs.add(doc);
    
    doc = new SolrInputDocument();
    doc.addField("id", 3);
    doc.addField("name", "Solr Input Documents 2");
    doc.addField("manu", "this is SolrInputDocuments 3 content");
    
    docs.add(doc);
    
    try {
        //add docs
        UpdateResponse response = server.add(docs);
        //commit後才保存到索引庫
        fail(server.commit());
        fail(response);
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    query("solr");
}

就是添加一個List集合

 

6、 添加JavaEntity Bean,這個需要先創建一個JavaBean,然後來完成添加操作;

JavaBean:Index的代碼

package com.hoo.entity;
 
import org.apache.solr.client.solrj.beans.Field;
 
/**
 * <b>function:</b> JavaEntity Bean;Index需要添加相關的Annotation註解,便於告訴solr哪些屬性參與到index中
 * @author hoojo
 * @createDate 2011-10-19 下午05:33:27
 * @file Index.java
 * @package com.hoo.entity
 * @project SolrExample
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class Index {
    //@Field setter方法上添加Annotation也是可以的
    private String id;
    @Field
    private String name;
    @Field
    private String manu;
    @Field
    private String[] cat;
 
    @Field
    private String[] features;
    @Field
    private float price;
    @Field
    private int popularity;
    @Field
    private boolean inStock;
    
    public String getId() {
        return id;
    }
    
    @Field
    public void setId(String id) {
        this.id = id;
    }
    //getter、setter方法
 
    public String toString() {
        return this.id + "#" + this.name + "#" + this.manu + "#" + this.cat;
    }
}

注意上面的屬性是和在apache-solr-3.4.0\example\solr\conf目錄下的schema.xml中可以找到有關於field屬性的配置對應的。如果你Index JavaBean中出現的屬性在schema.xml的field配置無法找到,那麼出出現unknown filed錯誤。

添加Bean完成doc添加操作

/**
 * <b>function:</b> 添加JavaEntity Bean
 * @author hoojo
 * @createDate 2011-10-21 上午09:55:37
 */
@Test
public void addBean() {
    //Index需要添加相關的Annotation註解,便於告訴solr哪些屬性參與到index中
    Index index = new Index();
    index.setId("4");
    index.setName("add bean index");
    index.setManu("index bean manu");
    index.setCat(new String[] { "a1", "b2" });
    
    try {
        //添加Index Bean到索引庫
        UpdateResponse response = server.addBean(index);
        fail(server.commit());//commit後才保存到索引庫
        fail(response);
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    queryAll();
}

 

7、 添加Bean集合

/**
 * <b>function:</b> 添加Entity Bean集合到索引庫
 * @author hoojo
 * @createDate 2011-10-21 上午10:00:55
 */
@Test
public void addBeans() {
    Index index = new Index();
    index.setId("6");
    index.setName("add beans index 1");
    index.setManu("index beans manu 1");
    index.setCat(new String[] { "a", "b" });
    
    List<Index> indexs = new ArrayList<Index>();
    indexs.add(index);
    
    index = new Index();
    index.setId("5");
    index.setName("add beans index 2");
    index.setManu("index beans manu 2");
    index.setCat(new String[] { "aaa", "bbbb" });
    indexs.add(index);
    try {
        //添加索引庫
        UpdateResponse response = server.addBeans(indexs);
        fail(server.commit());//commit後才保存到索引庫
        fail(response);
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    queryAll();
}

 

8、 刪除索引Document

/**
 * <b>function:</b> 刪除索引操作
 * @author hoojo
 * @createDate 2011-10-21 上午10:04:28
 */
@Test
public void remove() {
    try {
        //刪除id爲1的索引
        server.deleteById("1");
        server.commit();
        query("id:1");
        
        //根據id集合,刪除多個索引
        List<String> ids = new ArrayList<String>();
        ids.add("2");
        ids.add("3");
        server.deleteById(ids);
        server.commit(true, true);
        query("id:3 id:2");
        
        //刪除查詢到的索引信息
        server.deleteByQuery("id:4 id:6");
        server.commit(true, true);
        queryAll();
        
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

9、 查詢索引

/**
 * <b>function:</b> 查詢所有索引信息
 * @author hoojo
 * @createDate 2011-10-21 上午10:05:38
 */
@Test
public void queryAll() {
    ModifiableSolrParams params = new ModifiableSolrParams();
    // 查詢關鍵詞,*:*代表所有屬性、所有值,即所有index
    params.set("q", "*:*");
    // 分頁,start=0就是從0開始,,rows=5當前返回5條記錄,第二頁就是變化start這個值爲5就可以了。
    params.set("start", 0);
    params.set("rows", Integer.MAX_VALUE);
    
    // 排序,,如果按照id 排序,,那麼將score desc 改成 id desc(or asc)
    params.set("sort", "score desc");
 
    // 返回信息 * 爲全部 這裏是全部加上score,如果不加下面就不能使用score
    params.set("fl", "*,score");
    
    try {
        QueryResponse response = server.query(params);
        
        SolrDocumentList list = response.getResults();
        for (int i = 0; i < list.size(); i++) {
            fail(list.get(i));
        }
    } catch (SolrServerException e) {
        e.printStackTrace();
    }
}

 

10、 其他和Server有關方法

/**
 * <b>function:</b> 其他server相關方法測試
 * @author hoojo
 * @createDate 2011-10-21 上午10:02:03
 */
@Test
public void otherMethod() {
    fail(server.getBinder());
    try {
        fail(server.optimize());//合併索引文件,可以優化索引、提供性能,但需要一定的時間
        fail(server.ping());//ping服務器是否連接成功
        
        Index index = new Index();
        index.setId("299");
        index.setName("add bean index199");
        index.setManu("index bean manu199");
        index.setCat(new String[] { "a199", "b199" });
        
        UpdateResponse response = server.addBean(index);
        fail("response: " + response);
        
        queryAll();
        //回滾掉之前的操作,rollback addBean operation
        fail("rollback: " + server.rollback());
        //提交操作,提交後無法回滾之前操作;發現addBean沒有成功添加索引
        fail("commit: " + server.commit());
        queryAll();
    } catch (SolrServerException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

11、 文檔查詢

/**
 * <b>function:</b> query 基本用法測試
 * @author hoojo
 * @createDate 2011-10-20 下午04:44:28
 */
@Test
public void queryCase() {
    //AND 並且
    SolrQuery params = new SolrQuery("name:apple AND manu:inc");
    
    //OR 或者
    params.setQuery("name:apple OR manu:apache");
    //空格 等同於 OR
    params.setQuery("name:server manu:dell");
    
    //params.setQuery("name:solr - manu:inc");
    //params.setQuery("name:server + manu:dell");
    
    //查詢name包含solr apple
    params.setQuery("name:solr,apple");
    //manu不包含inc
    params.setQuery("name:solr,apple NOT manu:inc");
    
    //50 <= price <= 200
    params.setQuery("price:[50 TO 200]");
    params.setQuery("popularity:[5 TO 6]");
    //params.setQuery("price:[50 TO 200] - popularity:[5 TO 6]");
    //params.setQuery("price:[50 TO 200] + popularity:[5 TO 6]");
    
    //50 <= price <= 200 AND 5 <= popularity <= 6
    params.setQuery("price:[50 TO 200] AND popularity:[5 TO 6]");
    params.setQuery("price:[50 TO 200] OR popularity:[5 TO 6]");
    
    //過濾器查詢,可以提高性能 filter 類似多個條件組合,如and
    //params.addFilterQuery("id:VA902B");
    //params.addFilterQuery("price:[50 TO 200]");
    //params.addFilterQuery("popularity:[* TO 5]");
    //params.addFilterQuery("weight:*");
    //0 < popularity < 6  沒有等於
    //params.addFilterQuery("popularity:{0 TO 6}");
    
    //排序
    params.addSortField("id", ORDER.asc);
    
    //分頁:start開始頁,rows每頁顯示記錄條數
    //params.add("start", "0");
    //params.add("rows", "200");
    //params.setStart(0);
    //params.setRows(200);
    
    //設置高亮
    params.setHighlight(true); // 開啓高亮組件
    params.addHighlightField("name");// 高亮字段
    params.setHighlightSimplePre("<font color='red'>");//標記,高亮關鍵字前綴
    params.setHighlightSimplePost("</font>");//後綴
    params.setHighlightSnippets(1);//結果分片數,默認爲1
    params.setHighlightFragsize(1000);//每個分片的最大長度,默認爲100
 
    //分片信息
    params.setFacet(true)
        .setFacetMinCount(1)
        .setFacetLimit(5)//段
        .addFacetField("name")//分片字段
        .addFacetField("inStock"); 
    
    //params.setQueryType("");
    
    try {
        QueryResponse response = server.query(params);
        
        /*List<Index> indexs = response.getBeans(Index.class);
        for (int i = 0; i < indexs.size(); i++) {
            fail(indexs.get(i));
        }*/
        
        //輸出查詢結果集
        SolrDocumentList list = response.getResults();
        fail("query result nums: " + list.getNumFound());
        for (int i = 0; i < list.size(); i++) {
            fail(list.get(i));
        }
        
        //輸出分片信息
        List<FacetField> facets = response.getFacetFields();
        for (FacetField facet : facets) {
            fail(facet);
            List<Count> facetCounts = facet.getValues();
            for (FacetField.Count count : facetCounts) {
                System.out.println(count.getName() + ": " + count.getCount());
            }
        }
    } catch (SolrServerException e) {
        e.printStackTrace();
    } 
}

 

12、 分片查詢、統計

/**
 * <b>function:</b> 分片查詢, 可以統計關鍵字及出現的次數、或是做自動補全提示
 * @author hoojo
 * @createDate 2011-10-20 下午04:54:25
 */
@Test
public void facetQueryCase() {
    SolrQuery params = new SolrQuery("*:*");
    
    //排序
    params.addSortField("id", ORDER.asc);
    
    params.setStart(0);
    params.setRows(200);
 
    //Facet爲solr中的層次分類查詢
    //分片信息
    params.setFacet(true)
        .setQuery("*:*")
        .setFacetMinCount(1)
        .setFacetLimit(5)//段
        //.setFacetPrefix("electronics", "cat")
        .setFacetPrefix("cor")//查詢manu、name中關鍵字前綴是cor的
        .addFacetField("manu")
        .addFacetField("name");//分片字段
 
    try {
        QueryResponse response = server.query(params);
        
        //輸出查詢結果集
        SolrDocumentList list = response.getResults();
        fail("Query result nums: " + list.getNumFound());
        
        for (int i = 0; i < list.size(); i++) {
            fail(list.get(i));
        }
        
        fail("All facet filed result: ");
        //輸出分片信息
        List<FacetField> facets = response.getFacetFields();
        for (FacetField facet : facets) {
            fail(facet);
            List<Count> facetCounts = facet.getValues();
            for (FacetField.Count count : facetCounts) {
                //關鍵字 - 出現次數
                fail(count.getName() + ": " + count.getCount());
            }
        }
        
        fail("Search facet [name] filed result: ");
        //輸出分片信息
        FacetField facetField = response.getFacetField("name");
        List<Count> facetFields = facetField.getValues();
        for (Count count : facetFields) {
            //關鍵字 - 出現次數
            fail(count.getName() + ": " + count.getCount());
        }
    } catch (SolrServerException e) {
        e.printStackTrace();
    } 
}

分片查詢在某些統計關鍵字的時候還是很有用的,可以統計關鍵字出現的次數,可以通過統計的關鍵字來搜索相關文檔的信息。

 

四、Document文檔和JavaBean相互轉換

這裏轉換的Bean是一個簡單的User對象

package com.hoo.entity;
 
import java.io.Serializable;
import org.apache.solr.client.solrj.beans.Field;
 
/**
 * <b>function:</b> User Entity Bean;所有被添加Annotation @Field 註解的屬性將參與index操作
 * @author hoojo
 * @createDate 2011-10-19 下午04:16:00
 * @file User.java
 * @package com.hoo.entity
 * @project SolrExample
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class User implements Serializable {
 
    /**
     * @author Hoojo
     */
    private static final long serialVersionUID = 8606788203814942679L;
 
    //@Field
    private int id;
    @Field
    private String name;
    @Field
    private int age;
    
    /**
     * 可以給某個屬性重命名,likes就是solr index的屬性;在solrIndex中將顯示like爲likes
     */
    @Field("likes")
    private String[] like;
    @Field
    private String address;
    @Field
    private String sex;
    @Field
    private String remark;
    public int getId() {
        return id;
    }
    
    //setter 方法上面也可以
    @Field
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    //getter、setter
    
    @Override
    public String toString() {
        return this.id + "#" + this.name + "#" + this.age + "#" + this.like + "#" + this.address + "#" + this.sex + "#" + this.remark;
    }
}

 

測試類代碼如下

package com.hoo.test;
 
import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.junit.Test;
import com.hoo.entity.User;
 
/**
 * <b>function:</b>SolrInputDocument implements Map, Iterable
 * @author hoojo
 * @createDate 2011-10-19 下午03:54:54
 * @file SolrInputDocumentTest.java
 * @package com.hoo.test
 * @project SolrExample
 * @blog http://blog.csdn.net/IBM_hoojo
 * @email [email protected]
 * @version 1.0
 */
public class SolrInputDocumentTest {
 
    public final void fail(Object o) {
        System.out.println(o);
    }
    
    /**
     * <b>function:</b> 創建SolrInputDocument
     * @author hoojo
     * @createDate 2011-10-21 下午03:38:20
     */
    @Test
    public void createDoc() {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id", System.currentTimeMillis());
        doc.addField("name", "SolrInputDocument");
        doc.addField("age", 22, 2.0f);
        
        doc.addField("like", new String[] { "music", "book", "sport" });
        
        doc.put("address", new SolrInputField("guangzhou"));
        
        doc.setField("sex", "man");
        doc.setField("remark", "china people", 2.0f);
        
        fail(doc);
    }
    
    /**
     * <b>function:</b> 利用DocumentObjectBinder對象將SolrInputDocument 和 User對象相互轉換
     * @author hoojo
     * @createDate 2011-10-21 下午03:38:40
     */
    @Test
    public void docAndBean4Binder() {
        SolrDocument doc = new SolrDocument();
        doc.addField("id", 456);
        doc.addField("name", "SolrInputDocument");
        
        doc.addField("likes", new String[] { "music", "book", "sport" });
        
        doc.put("address", "guangzhou");
        
        doc.setField("sex", "man");
        doc.setField("remark", "china people");
        
        DocumentObjectBinder binder = new DocumentObjectBinder();
        
        User user = new User();
        user.setId(222);
        user.setName("JavaBean");
        user.setLike(new String[] { "music", "book", "sport" });
        user.setAddress("guangdong");
        
        fail(doc);
        // User ->> SolrInputDocument
        fail(binder.toSolrInputDocument(user));
        // SolrDocument ->> User
        fail(binder.getBean(User.class, doc));
        
        SolrDocumentList list = new SolrDocumentList();
        list.add(doc);
        list.add(doc);
        //SolrDocumentList ->> List
        fail(binder.getBeans(User.class, list));
    }
    
    /**
     * <b>function:</b> SolrInputDocument的相關方法
     * @author hoojo
     * @createDate 2011-10-21 下午03:44:30
     */
    @Test
    public void docMethod() {
        SolrInputDocument doc = new SolrInputDocument();
        doc.addField("id", System.currentTimeMillis());
        doc.addField("name", "SolrInputDocument");
        doc.addField("age", 23, 1.0f);
        doc.addField("age", 22, 2.0f);
        doc.addField("age", 24, 0f);
        
        fail(doc.entrySet());
        fail(doc.get("age"));
        //排名有用,類似百度競價排名
        doc.setDocumentBoost(2.0f);
        fail(doc.getDocumentBoost());
        fail(doc.getField("name"));
        fail(doc.getFieldNames());//keys
        fail(doc.getFieldValues("age"));
        fail(doc.getFieldValues("id"));
        fail(doc.values());
    }
}
轉自:http://www.cnblogs.com/hoojo/archive/2011/10/21/2220431.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章