solr 的客戶端調用solrj 建索引+分頁查詢

在 solr 3.5 配置及應用(一) 講過一了 solr 3.5的詳細配置,本節咱們講利用solr 的客戶端調用solr的應用了! java

1、利用SolrJ操做solr API

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

     須要的包以下:
    
     一、 apache-solr-solrj-3.5.0.jar

     二、 commons-httpclient-3.1.jar

     三、slf4j-api-1.6.0.jar

     四、commons-logging-1.1.jar

在solr 3.5的解壓包 apache-solr-3.5.0\apache-solr-3.5.0\dist\apache-solr-solrj-3.5.0.jar 就有這個包。

2、Solr理論

一、 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 內容保存在索引中。這對於檢索和醒目顯示內容頗有用,但對於實際搜索則不是必需的。例如,不少應用程序存儲指向內容位置的指針而不是存儲實際的文件內容。


二、 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更新是先刪除,而後添加索引。由於更新索引在必定狀況下,效率沒有先刪除後添加的效率好。

三、 搜索

添加文檔後,就能夠搜索這些文檔了。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


四、 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)

五、 索引配置

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

表 1 歸納了可控制 Solr 索引處理的各類因素:
因素
描述

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

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

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

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

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

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


六、 查詢處理配置

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

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

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

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

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

用於監視、記錄和統計數據的 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 中運行的全部線程的堆棧跟蹤信息。


八、 智能緩存

智能緩存是讓 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 會更多,只不過須要花更長的預熱時間。

3、實例

一、獲取SolrServer 用單例的形式寫了個類;


package com.stu.commons;
import java.net.MalformedURLException;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;

/**
* Description:
* @author  LiChunming
* @version V1.0
* @createDateTime:2012-2-27 下午03:49:04
* @Company: MSD.
* @Copyright : Copyright (c) 2011
**/
public class SolrServer {
    private static SolrServer solrServer = null;
    private static CommonsHttpSolrServer server=null;
    private static String url="http://localhost:8080/solr";

    public static synchronized SolrServer getInstance() {
        if (solrServer==null){
           solrServer=new SolrServer();
        }
        return solrServer;
    }
    public static CommonsHttpSolrServer getServer(){
         try {
            if(server==null){
              server = new CommonsHttpSolrServer(url);
              server.setSoTimeout(1000);  // socket read timeout
              server.setConnectionTimeout(1000);
              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.
            }
        } catch (MalformedURLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return server;
    }
}


二、打開目錄tomcat-7.0.14\solr\conf\schema.xml文件在<fields>下增長下字段。用於增長到搜索引擎的字段 <field name="blogId" type="string" indexed="true" stored="true"  required="true"/>
<field name="content" type="text" indexed="true" stored="true" omitNorms="true" />
<field name="bTypeId" type="string" indexed="true" stored="true" />
<field name="bTypeName" type="string" indexed="true" stored="true" />
<field name="nickName" type="string" indexed="true" stored="true" />
<field name="createTime" type="date" indexed="true" stored="true" omitNorms="true" />


三、增長信息到引擎文件中 public void writerBlog(BlogsDO blog) {
         // TODO Auto-generated method stub
          try {
              blog.setId(SerialNumberUtil.getRandomNum(4));
               //獲取鏈接服務
CommonsHttpSolrServer solrServer= SolrServer.getInstance().getServer();
               SolrInputDocument doc1 = new SolrInputDocument();
               doc1.addField("id", SerialNumberUtil.getRandomNum(4) );
               doc1.addField("blogId", blog.getBlogsId());
               doc1.addField("title",blog.getTitle()  );
               doc1.addField("bTypeId", blog.getbTypeId());
               doc1.addField("bTypeName", blog.getbTypeName());
               doc1.addField("content",  blog.getContent());
               String createTime=DateUtils.formatDate(blog.getGmtCreate(),  "yyyyMMddHHmmss");
               doc1.addField("createTime",createTime);
               doc1.addField("nickName",blog.getNickName());
               solrServer.add(doc1);
               solrServer.commit();
         } catch (SolrServerException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         } catch (IOException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
     }
實體類 BlogsDO  package com.stu.entity;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.apache.solr.client.solrj.beans.Field;

import com.stu.commons.util.DateUtils;

/**
  * Description:
  * @author  LiChunming
  * @version V1.0
  * @createDateTime:2011-5-17 下午04:38:11
  * @Company: MSD.
  * @Copyright : Copyright (c) 2011
  **/
@Entity
@Table(name="blogs")
public class BlogsDO implements Serializable{
     /**
      *
*/
     private static final long serialVersionUID = -4721368786493126226L;
     @Field
     private String id;
     @Field("blogId")
     private Integer blogsId;
     @Field
     private String title;
     @Field
     private String content="";
     @Field("createTime")
     private Date gmtCreate;
     @Field
     private String nickName;
     @Field
     private String bTypeId;
     @Field
     private String bTypeName;
     private Date gmtModified;
     private String revDate;
     private String sDate="";
     private String eDate="";

     @Transient
     public String getId() {
         return id;
     }
     public void setId(String id) {
         this.id = id;
     }
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     public Integer getBlogsId() {
         return blogsId;
     }
     public void setBlogsId(Integer blogsId) {
         this.blogsId = blogsId;
     }
     public String getTitle() {
         return title;
     }
     public void setTitle(String title) {
         this.title = title;
     }
     public String getContent() {
         return content;
     }
     public void setContent(String content) {
         this.content = content;
     }

     public String getNickName() {
         return nickName;
     }
     public void setNickName(String nickName) {
         this.nickName = nickName;
     }

     public String getbTypeId() {
         return bTypeId;
     }
     public void setbTypeId(String bTypeId) {
         this.bTypeId = bTypeId;
     }
     @Column(name="gmt_create")
     public Date getGmtCreate() {
         return gmtCreate;
     }
     public void setGmtCreate(Date gmtCreate) {
         this.gmtCreate = gmtCreate;
     }
     @Column(name="gmt_modified")
     public Date getGmtModified() {
         return gmtModified;
     }
     public void setGmtModified(Date gmtModified) {
         this.gmtModified = gmtModified;
     }
     @Transient
     public String getRevDate() {
          if (this.gmtCreate == null) {
                 return null;
             }
           return DateUtils.formatDate(gmtCreate, "yyyy-MM-dd HH:mm:ss");
     }
     public void setRevDate(String revDate) {
         this.revDate = revDate;
     }
     @Transient
     public String getbTypeName() {
         return bTypeName;
     }
     public void setbTypeName(String bTypeName) {
         this.bTypeName = bTypeName;
     }
     @Transient
     public String getsDate() {
         return sDate;
     }
     public void setsDate(String sDate) {
         this.sDate = sDate;
     }
     @Transient      public String geteDate() {          return eDate;      }      public void seteDate(String eDate) {          this.eDate = eDate;      }      @Override      public String toString() {          return this.id + "#" + this.blogsId + "#" + this.title + "#" + this.content + "#" + this.bTypeId + "#" + this.bTypeName + "#" + this.nickName+"#" + this.gmtCreate;      } } 五、文檔查詢(注意查詢出來的文檔轉化爲List<object>比較麻煩,下次咱們將使用 DocumentObjectBinder對象將SolrInputDocument 和 BlogsDO對象相互轉換) public List<BlogsDO> searchBlogsList(String content, String bTypeId,              String sDate, String eDate, Page page) throws IOException,              ParseException {           List<BlogsDO> blogList=new ArrayList<BlogsDO>();           BlogsDO blogsDO=null;           CommonsHttpSolrServer solrServer= SolrServer.getInstance().getServer();           SolrQuery sQuery = new SolrQuery();           String para="";          //OR 或者  OR 必定要大寫 if(StringUtils.isNotEmpty(content)){               para=para+"(title:"+content+" OR content:"+content+")";               //空格 等同於 OR // para=para+"(title:"+content+"  content:"+content+")";           }          //AND 而且  AND必定要大寫 if(!bTypeId.equals("-1")){               if(StringUtils.isNotEmpty(para)){                    para=para+" AND bTypeId:"+bTypeId;               }else{                    para=para+"  bTypeId:"+bTypeId;               }           }           if(StringUtils.isNotEmpty(sDate) && StringUtils.isNotEmpty(eDate)){               if(StringUtils.isNotEmpty(para)){                   para=para+" AND createTime:["+sDate+" TO "+eDate+"]";               }else{                   para=para+" createTime:["+sDate+" TO "+eDate+"]";               }          }          //查詢name包含solr apple //sQuery.setQuery("name:solr,apple"); //manu不包含inc //sQuery.setQuery("name:solr,apple NOT manu:inc"); //50 <= price <= 200 //sQuery.setQuery("price:[50 TO 200]"); //sQuery.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 //sQuery.setQuery("price:[50 TO 200] AND popularity:[5 TO 6]"); //sQuery.setQuery("price:[50 TO 200] OR popularity:[5 TO 6]"); // 查詢關鍵詞,*:*表明全部屬性、全部值,即全部index          if(!StringUtils.isNotEmpty(para)){               para="*:*";           }           logger.info("para:"+para);           sQuery.setQuery(para);           //設置分頁  start=0就是從0開始,,rows=5當前返回5條記錄,第二頁就是變化start這個值爲5就能夠了。 sQuery.setStart((page.getCurrentPage()-1)*page.getPerPageSize());           sQuery.setRows(page.getPerPageSize());           //排序 若是按照blogId 排序,,那麼將blogId desc(or asc) 改爲 id desc(or asc)           sQuery.addSortField("blogId", ORDER.asc);           //設置高亮 sQuery.setHighlight(true); // 開啓高亮組件 sQuery.addHighlightField("content");// 高亮字段 sQuery.addHighlightField("title");// 高亮字段 sQuery.setHighlightSimplePre("<font color='red'>");//標記,高亮關鍵字前綴 sQuery.setHighlightSimplePost("</font>");//後綴 sQuery.setHighlightSnippets(2);//結果分片數,默認爲1          sQuery.setHighlightFragsize(1000);//每一個分片的最大長度,默認爲100 //分片信息 sQuery.setFacet(true)              .setFacetMinCount(1)              .setFacetLimit(5)//段 .addFacetField("content");//分片字段 try {              QueryResponse response = solrServer.query(sQuery);              SolrDocumentList list = response.getResults();              Integer counts=(int) list.getNumFound();              logger.info("counts:"+counts);              page.setCounts(counts);              //獲取全部高亮的字段 Map<String,Map<String,List<String>>> highlightMap=response.getHighlighting();              String blogId="";              for (SolrDocument solrDocument : list) {                  blogsDO=new BlogsDO();                  blogId=solrDocument.getFieldValue("blogId").toString();                  blogsDO.setBlogsId(Integer.valueOf(blogId));                  blogsDO.setbTypeId(solrDocument.getFieldValue("bTypeId").toString());                  blogsDO.setbTypeName(solrDocument.getFieldValue("bTypeName").toString());                  blogsDO.setNickName(solrDocument.getFieldValue("nickName").toString());                  List<String> titleList=highlightMap.get(blogId).get("title");                  List<String> contentList=highlightMap.get(blogId).get("content");                  if(titleList!=null && titleList.size()>0){                      blogsDO.setTitle(titleList.get(0));                  }else{                      //獲取並設置高亮的字段title                      blogsDO.setTitle(solrDocument.getFieldValue("title").toString());                  }                  if(contentList!=null && contentList.size()>0){                      blogsDO.setContent(contentList.get(0));                  }else{                      //獲取並設置高亮的字段content                      blogsDO.setContent(solrDocument.getFieldValue("content").toString());                  }                  blogsDO.setRevDate(solrDocument.getFieldValue("createTime").toString());                  SimpleDateFormat sdf =  new  SimpleDateFormat("yyyyMMddHHmmss");                  try {                      blogsDO.setGmtCreate(sdf.parse(solrDocument.getFieldValue("createTime").toString()));                  } catch (java.text.ParseException e) {                      // TODO Auto-generated catch block                      e.printStackTrace();                  }                  blogList.add(blogsDO);              }          } catch (SolrServerException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }          return blogList;      }          
相關文章
相關標籤/搜索