solrj的基本使用

先說說什麼是solr,我從百度上覆制了一點定義:
html

Solr是一個獨立的企業級搜索應用服務器,它對外提供相似於Web-service的API接口。用戶能夠經過http請求,向搜索引擎服務器提交必定格式的XML文件,生成索引;也能夠經過Http Get操做提出查找請求,並獲得XML格式的返回,Solr是一個高性能,採用Java5開發,基於Lucene的全文搜索服務器。同時對其進行了擴展,提供了比Lucene更爲豐富的查詢語言,同時實現了可配置、可擴展並對查詢性能進行了優化,而且提供了一個完善的功能管理界面,是一款很是優秀的全文搜索引擎java

多了我就不復制了,你們能夠網上搜搜看,我這裏把我如何用到solr,已經用到了solr中的哪些地方和如何使用展現出來,solr的功能很是強大,可是我用到的到不是不少。web

下面我按照需求-思路-實現來說解solr的使用,這是我生平第一次使用solr,不到之處還望多多指正:redis


需求緣由:系統數據量太大,根據系統業務邏輯須要,sql已經沒有多少優化空間,併發量太大和過大的數據量致使查詢響應速度緩慢,頁面加載緩慢,用戶體驗很是很差。算法

需求:系統原來的邏輯是:redis->DB,修改成redis->solr->DBsql

這樣設計的緣由:數據庫

一、首先同等併發量和數據量的狀況下,solr檢索出結果集的速度遠大於關係型數據庫(具體緣由涉及到檢索排序算法<solr是基於倒排序算法的> 等多種緣由)apache

二、從solr服務器中讀取數據,就避免了訪問訪問DB,除非solr服務器出現問題,這個機率不是很大tomcat

固然主要緣由仍是第一個,查詢速度快,用戶體驗會上升服務器


開發思路:既然系統要接入solr,本身首先得弄清楚原理,爲何solr能夠看成數據庫來使用。

solr是企業級搜索服務器,提供對數據的存儲,數據索引建立等一系列功能。好比對一條數據建立索引的時候,同時將這個數據保存到solr服務器,就能夠根據索引數據查詢到整條數據,知道solr能夠看成容器同樣存放數據,除此以外,solr還提供了跟DB相似的功能-對數據的增刪改查,知道這些,就知道爲何能夠「替代」DB了吧。

代碼開發階段:

1、開發環境的搭建:

solr是獨立的服務器,因此咱們須要搭建環境,我使用的應用服務器是tomcat,將solr接到tomcat服務器中便可,至於怎麼配置的,我這裏不想詳細說明了,網上配置這個東西的就像配置JDK環境變量同樣多,你們本身動手,豐衣足食,我會上傳一個我配置好solr功能的tomcat服務器,你們能夠直接下載,地址:http://download.csdn.net/detail/duyunduzai/7286411

2、代碼開發:

我代碼中是將solr封裝成一個接口,只須要調用對索引增刪改查的接口便可。使用的是solrj系類功能,下面會針對代碼詳細講述。

一、建立接口

/**
 * 
 * 前面文章中說過了,是對系統的優化,假如以前數據庫中保存的數據是用戶的信息
 * User:username,age,email,custNo;
 *
 */
public interface ISolrUserService {

    /**
     *  一句話功能描述:建立用戶索引數據
     */
    public boolean createUserIndex(User user);
    /**
     * 一句話功能描述:批量建立用戶索引數據
     */
    public boolean createUserIndex(List<User> userLists);
    /**
     * 一句話功能描述:批量刪除用戶索引數據
     */
    public boolean deleteUserIndex(List<String> custNos);
    /**
     * 一句話功能描述:查詢用戶數據
     */
    public QueryResult<SearchUserDTO> queryUser(SearchPage page);
}


上面的代碼是一個接口,其中包含的是對用戶數據索引的建立、刪除和查詢,User類是用戶信息類,包含四個屬性:username,age,email,custNo,建立對應的實體類:

public class User {
	private String custNo;
	private String username;
	private String email;
	private int age; 
    
	// 省略get和set方法
}


第一個代碼片斷中的QueryResult是我寫的查詢結果類,SearchPage是一個查詢條件類,我先把代碼貼出來給你們看看:

/**
 * 
 * 功能描述: 查詢結果基類
 */
public class QueryResult<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private List<T>           datas;

    private Boolean           isLastPage;

    private Integer           totalDataCount;

    private int               pageNumber       = 1;

    private int               pageSize         = 10;

    private Integer           pageCount;

    private int               indexNumber;

    /**
     * @param totalDataCount 總數據件數
     * @param pageSize 每頁顯示條數
     * @param pageNumber 當前的頁數
     */
    public QueryResult(int totalDataCount, int pageSize, int pageNumber) {
        super();
        this.totalDataCount = totalDataCount;
        this.pageSize = pageSize;
        this.pageNumber = pageNumber;
        if (this.pageNumber < 1) {
            this.pageNumber = 1;
        }
        if (this.totalDataCount <= 0) {
            return;
        }
        // 若是查詢頁數大於總頁數,則取最後一頁
        if (this.totalDataCount <= (this.pageNumber - 1) * this.pageSize) {
            this.pageNumber = (this.totalDataCount + this.pageSize - 1) / this.pageSize;
        }
        this.indexNumber = (this.pageNumber - 1) * this.pageSize;
        // 總頁數
        this.pageCount = (this.totalDataCount + this.pageSize - 1) / this.pageSize;
        // 是否爲最後一頁
        this.isLastPage = (this.pageNumber == this.pageCount ? true : false);
    }

    public QueryResult() {
        super();
    }

    /**
     * 返回的數據集
     * @return the datas
     */
    public List<T> getDatas() {
        return datas;
    }

    /**
     * @param datas the datas to set
     */
    public void setDatas(List<T> datas) {
        this.datas = datas;
    }

    /**
     * 知足查詢條件的總記錄數, null 意味着未知。注:只在查詢第一頁時返回正確的總記錄數,其它頁碼時,返回-1
     * @return the totalDataCount
     */
    public Integer getTotalDataCount() {
        return totalDataCount;
    }

    /**
     * @param totalDataCount the totalDataCount to set
     */
    public void setTotalDataCount(Integer totalDataCount) {
        this.totalDataCount = totalDataCount;
    }

    /**
     * 頁碼,從1開始
     * @return the pageNumber
     */
    public int getPageNumber() {
        return pageNumber;
    }

    /**
     * @param pageNumber the pageNumber to set
     */
    public void setPageNumber(int pageNumber) {
        this.pageNumber = pageNumber;
    }

    /**
     * 知足查詢條件的總頁數, null 意味着未知。注:只在查詢第一頁時返回正確的總記錄數,其它頁碼時,返回-1
     * 
     * @return the pageCount
     */
    public Integer getPageCount() {
        return pageCount;
    }

    /**
     * @param pageCount the pageCount to set
     */
    public void setPageCount(Integer pageCount) {
        this.pageCount = pageCount;
    }

    /**
     * 每頁大小,缺省爲10條記錄/頁
     * @return the pageSize
     */
    public int getPageSize() {
        return pageSize;
    }

    /**
     * @param pageSize the pageSize to set
     */
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    /**
     * 標誌是否最後一頁,True: 是最後一頁,False: 不是,null:未知
     * @return the lastPage
     */
    public Boolean getIsLastPage() {
        return isLastPage;
    }

    /**
     * @param lastPage the lastPage to set
     */
    public void setIsLastPage(Boolean lastPage) {
        this.isLastPage = lastPage;
    }

    /**
     * 計算開始數
     * @return the lastPage
     */
    public int getIndexNumber() {
        return indexNumber;
    }
}


上面是查詢結果基類,還有一個類是查詢條件類

public class SearchPage implements Serializable {

    private static final long serialVersionUID = 1L;

    /** 頁碼 */
    private int               pageNumber       = 1;

    /** 每頁記錄數 */
    private int               pageSize         = 10;

    /** 總記錄數 */
    private int               totalCount;

    /** 排序字段 */
    private String[]          orderType;

    /**
     * 查詢關鍵字
     */
    private String            keyword;

    /** 多條件查詢 */
    private String            selectParam;

    /** 默認查詢字段 */
    private String            field;

    private int               Start            = 0;
	// 省略get和set
    
}


上面貼了這麼多代碼,其實都是跟solr無關的代碼,不過能夠完整展示個人代碼邏輯,方便你們給我指正

接下來是對isolrUserService接口的實現類,在實現類中就是具體的實現如何對數據建立索引,刪除索引等操做

@Service
public class SolrUserServiceImpl implements IsolrUserService {

    private static final Logger   LOGGER = LoggerFactory.getLogger(SolrUserServiceImpl.class);

    private static HttpSolrServer httpSolrServer;
	/** httpServer 是用來鏈接solr服務器,這裏採用單例模式設計 */
    private static HttpSolrServer getHttpSolrServer() {
        if (httpSolrServer == null) {
			/** 用戶(User)數據solr服務地址 */
            httpSolrServer = new HttpSolrServer("http://10.22.10.110:8983/solr/user");
			/** 設置solr查詢超時時間 */
            httpSolrServer.setSoTimeout(1000);
			/** 設置solr鏈接超時時間 */
            httpSolrServer.setConnectionTimeout(1000);
			/** solr最大鏈接數 */
            httpSolrServer.setDefaultMaxConnectionsPerHost(1000);
			/** solr最大重試次數 */
            httpSolrServer.setMaxRetries(1);
			/** solr全部最大鏈接數 */
            httpSolrServer.setMaxTotalConnections(100);
			/** solr是否容許壓縮 */
            httpSolrServer.setAllowCompression(false);
            /** solr是否followRedirects */
            httpSolrServer.setFollowRedirects(true);
        }
        return httpSolrServer;
    }
    @Override
    public boolean createUserIndex(User user) {
        // 獲取solr服務
        SolrServer solrServer = getHttpSolrServer();
        try {
            // 建立索引,由於solr建立索引的時候,在參數類中的屬性上面須要註解@Field,
			//因此,要將user類轉換成能夠建立索引的類,我單首創建了一個類,對應User,
			// SearchUserDTO.java,跟User類屬性同樣,只是在各個屬性上面添加@Field註解
            solrServer.addBean(toSearchUser(user));
			// 提交建立。就至關於DB中的commit
            solrServer.commit();
            return true;
        } catch (IOException e) {
            LOGGER.error("", e);
        } catch (SolrServerException e) {
            LOGGER.error("", e);
        }
        return false;
    }
    private SearchUserDTO toSearchUser(User user) {
        SearchUserDTO userDTO = new SearchUserDTO();
		// 此方法是將user中屬性的值複製到userDTO屬性,這個方法是複製類中屬性名同樣的屬性值
        BeanUtils.copyProperties(user, userDTO);
        return userDTO;
    }
    @Override
    public boolean createUserIndex(List<User> users) {
        if (users != null && users.size() > 0) {
            List<SearchUserDTO> datas = new ArrayList<SearchUserDTO>(users.size());
            for (User user : users) {
                datas.add(toSearchUser(user));
            }
            SolrServer solrServer = getHttpSolrServer();
            try {
                // 批量建立評價回覆索引數據
                solrServer.addBeans(datas);
                solrServer.commit();
                return true;
            } catch (IOException e) {
				// 若是建立失敗的話,能夠回滾
				// solrServer.collback();
                LOGGER.error("", e);
            } catch (SolrServerException e) {
                LOGGER.error("", e);
            }
        }
        return false;
    }
    @Override
    public boolean deleteUserIndex(List<String> custNos) {
        SolrServer solrServer = getHttpSolrServer();
        try {
			// 根據惟一性標識刪除索引
            solrServer.deleteById(custNos);
			// 刪除該核下全部索引
			// solrServer.delete("*:*");
            solrServer.commit();
            return true;
        } catch (IOException e) {
            LOGGER.error("", e);
        } catch (SolrServerException e) {
            LOGGER.error("", e);
        }
        return false;
    }
    @Override
    public QueryResult<SearchUserDTO> queryUser(SearchPage pager) {
        QueryResult<SearchUserDTO> queryResult = new QueryResult<SearchUserDTO>();
        QueryResponse response = null;
        // 設置默認查詢條件,格式爲:field:keyword,好比:"custNo:1234"
        String searchParam = pager.getField() + ":" + pager.getKeyword();
        SolrServer server = getHttpSolrServer();
        SolrQuery query = new SolrQuery(searchParam);
        // 設置限制條件查詢,假如同時查詢username爲zhangsan的用戶,這裏查詢條件格式我就暫很少說了,下面會和配置文件一塊兒來講一下
        // query.setFilterQueries("username:zhangsan");
        query.setFilterQueries(pager.getSelectParam());
        query.setStart(pager.getStart()); // 起始位置,用於分頁,solrj中默認是每頁10條數據
        query.setRows(pager.getPageSize()); // 每頁文檔數
        try {
            response = server.query(query);
        } catch (SolrServerException e) {
            LOGGER.error("查詢索引出現問題", e);
        }
        if (response != null) {
            SolrDocumentList list = response.getResults();
            List<SearchUserDTO> datas = new ArrayList<SearchUserDTO>();
            setSearchUserDTOData(datas, list);
            queryResult.setTotalDataCount(new Long(list.getNumFound()).intValue());
            queryResult.setPageNumber(pager.getPageNumber());
            queryResult.setPageSize(pager.getPageSize());
            queryResult.setDatas(datas);
        }
        return queryResult;
    }
    private void setSearchUserDTOData(List<SearchUserDTO> datas, SolrDocumentList list) {
        for (SolrDocument solrDocument : list) {
            SearchUserDTO userDTO = new SearchUserDTO();
			// 根據屬性名稱從返回結果中取得數據,而且封裝到返回對象中
            SearchUserDTO.setUsername(solrDocument.getFieldValue("username").toString());
            SearchUserDTO.setEmail(solrDocument.getFieldValue("email").toString());
            SearchUserDTO.setCustNo(solrDocument.getFieldValue("custNo").toString());
			SearchUserDTO.setAger((Integer)solrDocument.getFieldValue("age"));
            datas.add(SearchUserDTO);
        }
    }
}


上面的代碼是實現了對索引的建立,刪除以及查詢,對索引的更新操做其實就是對索引的從新建立,就像redis中建立鍵值時同樣的,建立已存在的健的話新值就是把原來的值覆蓋掉。你們確定會有疑問,solr是如何把類的字段跟索引鏈接到一塊兒的呢,其實這裏面是有一個配置文件的,下面重點講講這個配置文件:


solr服務支持單核和多核,假如只是對一張表進行建立索引數據,只使用單核就能夠了,可是若是你對用戶表建立索引數據,同時又對商品信息表建立索引數據,就須要對這兩個表的索引數據放在不一樣 的地址下面,這裏就利用到了solr多核,我上傳到tomcat-solr服務器就是多核配置


解壓tomcat-solr,在../apache-tomcat-7.0.26-master\webapps\solr\conf\multicore\下面有一個solr.xml,這裏配置的是多核信息,在user、productInfo表示兩個核,這裏以user爲例,user文件夾下有兩個文件夾,conf和data,其中data中存放的是索引文件,這個就很少說了,索引文件的格式,內容等是solrj建立索引的時候寫到裏面去的,說一下conf問價夾,conf下面有三個文件:solrconfig.xml,scheam.xml和dataimport.properties,其中solrconfig.xml和dataimport.properties是能夠配置不少功能的,可是我這個例子中只須要用到schema.xml配置,其他兩個用默認就好了,重點講下schema.xml:

<?xml version="1.0" ?>

<schema name="example core user" version="1.1">
  <types>
    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
      <!--add IKAnalyzer configuration-->
      <fieldType name="textik" class="solr.TextField" >
        <analyzer type="index">
          <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" isMaxWordLength="false" useSmart="false"/>
        </analyzer>
        <analyzer type="query">
          <tokenizer class="org.wltea.analyzer.solr.IKTokenizerFactory" isMaxWordLength="false" useSmart="false"/>
        </analyzer>
    </fieldType>
	<fieldType name="int" class="solr.TrieIntField" precisionStep="0" positionIncrementGap="0"/>
  </types>

  <fields>
    <field name="username"      type="textik" indexed="true"  stored="true" multiValued="false" required="true"/>
    <field name="custNo" type="string" indexed="true"  stored="true" multiValued="false" required="true" /> 
    <field name="email"     type="string" indexed="true" stored="true" multiValued="false" />
	<field name="age"     type="int" indexed="false" stored="true" multiValued="false" />
  </fields>

  <!-- field to use to determine and enforce document uniqueness. -->
  <uniqueKey>custNo</uniqueKey>

  <!-- field for the QueryParser to use when an explicit fieldname is absent -->
  <defaultSearchField>username</defaultSearchField>

  <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
  <solrQueryParser defaultOperator="OR"/>
</schema>


這裏面配置的是一些字段信息,從上到下依次是types,fields,uniquekey,defaultSearchField,solrQueryParser,還有其餘的一些配置的,咱們這裏沒用到而已

types:這裏面配置的是字段類型,就像java中的int,long,String等,用fieldType標籤表示,以String爲例:

<fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>

name:FieldType的名稱

class:指向org.apache.solr.analysis包裏面對應的class名稱,用來定義這個類型的行爲

sortMissingLast:設置成true沒有該field的數據排在有該field的數據以後,而無論請求時的排序規則, 默認是設置成false。 sortMissingFirst 跟上面倒過來唄

omitNorms:true 則字段檢索時被省略相關的規範

fields:

name:字段名稱
type:類型,此處寫的類型必須在fieldType中聲明,假如你type=long,在fieldType中就要有對應的long的聲明(<fieldtype name="long" class="solr.LongField" omitNorms="true"/>),否則啓動服務時會報錯
indexed:是否建立索引,true表示建立,默認false,加入你username的indexed=false,那麼你用username來查詢索引是查詢不到的
stored:是否保存數據,若是false的話,就查詢返回結果中該字段數據就沒有,這個屬性在設計的時候是要注意的,有寫字段不須要的話能夠設爲false,能夠減少索引件的大小
multiValued:是否多值,true的話就是多值,假如username的multiValued=true,在索引文件中查出的結果就是username['zhangsan','lisi'..]
required:是否必須,假如username的required=true,那麼在建立索引的時候,username就必須有值,不然建立不成功
固然,field還包含其餘不少屬性,好比默認值defaule,是否壓縮compressed等就很少寫了,由於我沒用到。

uniqueKey:惟一性主鍵

defaultSearchField:默認使用該字段查詢,可是咱們每每查詢的時候是根據本身須要查詢的,好比咱們查詢條件爲:username:zhangsan,若是直接寫成zhangsan的話,查詢條件就是:"defauleSearchField:zhangsan"

solrQueryParser:設置默認操做,OR仍是AND,加入咱們設置<solrQueryParser defaultOperator="OR"/>,咱們查詢username:zhangsan lisi ,就至關於username=zhangsan OR username=lisi,若是這個地方咱們設置成and的話,就是username=zhagnsan and username=lisi

配置文件這裏就講完了,如今你們應該懂了吧,下面加一點查詢擴展

/*設置查詢條件
	*查詢全部:*:*
	*按照名稱查詢:username:zhangsan
	*按照email查詢:email:1234@sina.cn
	*咱們假設按照名稱查詢
	*/ 
	SolrQuery query = new SolrQuery("username:zhangsan");
        /*
	*設置限制級查詢條件
	*假如咱們同時查出email是1234@sina.cn的
	*
	*這裏面也能夠多條件,假如email是1234@sina.cn同時年齡是21
	*就應該寫成:email:1234@sina.cn AND age:21(注意這裏面的AND和OR必須大寫)
	*/
        query.setFilterQueries("email:1234@sina.cn AND age:21");
	/*
	*設置排序
	*假如要求按照名稱降序
	*/
	query.addSortField("username", ORDER.desc);
	query.addSortField(username, order);
        query.setStart(pager.getStart()); // 起始位置,用於分頁
        query.setRows(pager.getPageSize()); // 每頁文檔數
	response = server.query(query);

// 歸納一下上面的查詢條件就是:username=zhangsan,age=21 email=1234@sina.cn,按照username降序排列(上面age的indexed=false,應該改成true,才能按照age索引)


根據這些,基本上能夠知足對數據的條件查詢了,若是是多表關聯查詢的話,我如今只知道從一個索引文件中查到數據,而後根據標識去另外一個索引中查找,沒發現有能夠替代DB中的join表的功能,solr中還有不少功能,有高亮顯示查詢結果,根據各類複雜的算法解析啊等,第一次接觸solr,就寫到這兒吧,望各位指出不足之處

相關文章
相關標籤/搜索