天天看點

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

                                     solr之搜尋引擎

                                                                                                                  文 / 汝淉

都是個人自己寫的,肯定會有一些不當的地方,還希望海涵,當然指出來多交流

如若轉載,請說明出處…原創不易…謝謝支援

一:solr的介紹

       目前市面上流行的搜素引擎有以Solr 和ElasticSearch領銜的等等好幾種…他們的共同點都是能處理千萬以及上億級别的資料搜尋和存儲…功能很強大,資料量越大,就得采用叢集發方式,會讓性能得到很大的提升…

       說到Solr 和ElasticSearch不得不提Lucene.一個功能強大的檢索引擎,Solr 和ElasticSearch都是基于Lucene的…是以就有必要提提Lucene.

       Lucene: 全稱"全文檢索引擎".是一款高性能的、可拓展的資訊檢索(IR)工具庫,

提供了完整的查詢引擎和索引引擎…他有以下幾種操作類型…

  • IndexWriter :負責建立新索引或者打開已有索引,以及向索引中添加、删除或更新被索引文檔的資訊。
  • Directory:描述了Lucene索引的存放位置。它是一個抽象類,它的子類負責具體指定索引的存儲路徑。
  • Analyzar:負責從被索引文本檔案中提取詞彙單元,并提出剩下的無用資訊。分析器的分析對象為文檔,該文檔包含一些分離的能被索引的域。
  • Document:文檔對象代表一些域(Field)的集合。Lucene隻處理從二進制文檔中提取的以Field執行個體出現的文本
  • Field:指包含能被索引的文本内容的類。
  • IndexSearcher:用于搜尋由IndexWriter類建立的索引。
  • Term:搜尋功能的基本單元。與Field對象類似,Term對象包含一對字元串元素:域名和單詞(或域文本值)。
  • Query: 查詢類。
  • TermQuery:最基本的查詢類型,也是簡單查詢類型之一。用來比對指定域中包含特定項的文檔。
  • TopDocs:一個簡單的指針容器,指針一般指向前N個排名的搜尋結果,搜尋結果即比對查詢條件的文檔。TopDocs會記錄前N個結果中每個結果的int docID(可以用它來恢複文檔)和浮點型分數。

           其實,Solr與Lucene 并不是競争對立關系,恰恰相反Solr 依存于Lucene,因為Solr底層的核心技術是使用Lucene 來實作的,Solr和Lucene的本質差別有以下三點:搜尋伺服器,企業級和管理。Lucene本質上是搜尋庫,不是獨立的應用程式,而Solr是。Lucene專注于搜尋底層的建設,而Solr專注于企業應用。Lucene不負責支撐搜尋服務所必須的管理,而Solr負責。

一句話概括 Solr: Solr是Lucene面向企業搜尋應用的擴充。

      這些相關标志在後面的Solr界面會有展現.詳細介紹可網上查詢.

      以Solr為例.Solr基于Lucene,繼承了Lucene的強大的檢索能力. …下面重點介紹Solr.

      Solr:是一個高性能,采用Java5開發,Solr基于Lucene的全文搜尋伺服器。同時對其進行了擴充,提供了比Lucene更為豐富的查詢語言,同時實作了可配置、可擴充并對查詢性能進行了優化,并且提供了一個完善的功能管理界面,是一款非常優秀的全文搜尋引擎。

以下是個人使用見解

      個人使用見解: Solr提供了友善的"管理者"操作界面,大大友善了"管理者的操作",使得變的非常靈活 和友善,他有索引建立和索引檢索2個強大的功能…

      **索引建立:**他有自己提供的NoSql的索引庫,可以把資料從資料庫增量到索引庫中,對每條資料生成索引,進行存儲…

      **索引檢索:**友善查詢,是以才會使千萬級别的資料查詢變的很快…靈活的參數設定和完美的結果展示,使使用者體驗非常好…,上面這2大功能都能在Solr界面展示出來…以圖為例

看的時候, 最左側都是目錄 右側一大片都是query的操作詳情界面

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

      非常友善和靈活的操作界面,後面會逐個詳解各個操作位置的詳情.

二:solr的安裝部署

     1. 所需工具solr6.6.2 tomcat8 jdk8 Ik-Analyzer 本地就正常解壓就行.放到tomcat容器中部署.(都可去官網下載下傳)…

solr自己找的下載下傳位址: http://mirrors.shuosc.org/apache/lucene/solr/ (可選擇自己想要的版本)

   2. 在解壓後的檔案solr-6.6.2中找到solr-6.6.2\server\solr-webapp下的webapp檔案夾,然後将其複制到tomcat8\webapps目錄下 并起名叫solr

   3.把solr-6.6.2\server\lib\ext 下所有的jar包和 solr-6.6.2\dist 下的solr-dataimporthandler-6.4.1.jar、solr-dataimporthandler-extras-6.4.1.jar2個檔案複制到Tomcat8\webapps\solr\WEB-INF\lib路徑中…

      4. 建立solr-home 把solr-6.6.2\server目錄下的solr複制到其他目錄地方…(我本地的位置是D:/soft/apache-tomcat-8.0.39/solr-home)…并把該solr起名叫solr-home (索引庫存放地)這是solr核心檔案夾。

       5. 到tomcat8\webapps\solr\WEB-INF下的web.xml 在檔案中找到如下内容…取消其注釋.

<env-entry>
       <env-entry-name>solr/home</env-entry-name>
        <env-entry-value>D:/solr/apache-tomcat-8.0.39/solr-home</env-entry-value>
        <env-entry-type>java.lang.String</env-entry-type>
           </env-entry>
這裡是配置solr-home的位置.如我的位址是本地的位址D:/solr/apache-tomcat-8.0.39/solr-home
           

      6.将solr-6.6.2\server\lib下的5個metrics開頭的jar包複制到Tomcat8\webapps\solr\WEB-INF\lib下.

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

      7. 将solr-6.6.2\server\resources下的log4j.properties 複制到Tomcat8\webapps\solr\WEB-INF下的classes檔案夾下,此檔案夾手動建立.

      8.去掉tomcat的權限…找到Tomcat8\webapps\solr\WEB-INF下的web.xml檔案…注釋掉下面這段文字

<!--
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Disable TRACE</web-resource-name>
      <url-pattern>/</url-pattern>
      <http-method>TRACE</http-method>
    </web-resource-collection>
    <auth-constraint/>
  </security-constraint>
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Enable everything but TRACE</web-resource-name>
      <url-pattern>/</url-pattern>
      <http-method-omission>TRACE</http-method-omission>
    </web-resource-collection>
  </security-constraint>
  -->
           

      然後這就是solr部署安裝成功. 可以啟動用戶端嘗試通路一下.不做其他修改,就是下面這個位址,就是solr的操作界面, http://localhost:10003/solr/index.html (端口号自己tomcat的端口号我的是10003)

三:建立Solr core 之索引庫.(手動操作檔案夾,不是線上直接建立)

Solr core是Solr-Home下的索引存儲地…可以建立多個,一個core有以下目錄

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

1. 需要建立這個core.找到solr-6.6.2\example\example-DIH\solr 下的db檔案夾…将其複制到solr-home中.并起名叫mycore(随便起) 注意mycore/conf中的schema-manaed檔案.此檔案是配置solr的索引資料. (這個一會會做詳細說明)

2.找到mycore/conf中的solrconfig.xml檔案…找到此處

<requestHandler name="/dataimport" class="solr.DataImportHandler">
            <lst name="defaults">
            <str name="config">data-config.xml</str>
             </lst>
             </requestHandler>
   其實隻要這裡配置的檔案名稱和mycore/conf中的data-config.xml名字一樣就行..
把該檔案名稱配置到這裡即可. .該檔案是solr索引庫中增量的配置地方---也就是資料庫導入索引庫需要配置的地方
           

到此處基本上就core就建立好了.

接下來需要對mycore/conf中的幾個檔案做詳細講解,包括各自的作用.

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

圖中标記的前2個檔案十分重要,下面那個改動一點點, 在Solr5.5版本之前,managed-schema 叫schema.xml…5.5版本之後才改成managed-schema .而網上大部分文檔還是5.5以前的,是以會是schema.xml…接下來就解開managed-schema這個檔案的神秘面紗.

之前提了這裡是索引庫中對字段的設定,是以就顯得十分重要…他的内部内容很多,但是無需關心那些,隻要關注3個标簽 .

  • fieldType(自帶的分詞類型.有很多分詞器,但是沒有很好支援中文的分詞器.).
  • field:這裡是配置單個字段的地方,着重配置這裡
  • copyField:這裡是配置字段的域…也很重要.

上截圖

這裡面有個地方,對于searchkey這個多值域,有了更加靈活的方法,後期直接在solr用戶端query中進行查詢語句比對,無需像我這樣進行固定式配置,麻煩而且不靈活…講完這個,後面就講多值多域比對…

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

介紹上面截圖中field标簽中的屬性

field 介紹上面截圖中field标簽中的屬性
name 和資料庫中的字段對應起别名,導入進索引庫以後對應的字段名就是這裡配置的如goods_name 對應這裡可以起别名goodsName
type 這裡配置字段的類型,要和資料庫中的字段類型一緻,選取的時候,其實上方配置都有定義類型,要注意和配置的一緻,比如string上方定義的時候是小寫,我們就别寫成大寫S了至于text_ik類型,後續會講解
multiValued 是否是多值,比如 goodsColor 可以使紅,黃,藍…是以就看需不需要多值存儲了.存進索引庫都是以逗号分割…
indexed 是否需要索引,隻有需要當做域(比如根據goodsName:益生菌)來查詢,包括當做where條件來比對的字段有必要添加索引,其他可以不用建立索引.
stored 是否需要存儲到索引庫,一般需要傳回給使用者的都需要被存儲,
copyField 介紹上面截圖中copyField标簽中的屬性(可以了解成設定域)
source 這裡就是配置field中name屬性的值,如果寫的上面field未定義的會報錯.
dest 這裡是設定域,需要一個多值的字段就行…

多值多域比對

需求:相對多個字段同時進行檢索,隻要其中一個字段含有該關鍵字就比對出記錄,這裡就有 大寫的 OR 來組裝查詢語句

查詢既滿足A字段 又滿足B字段的記錄…則 查詢語句為 A AND(一定要大寫) B :輸入的關鍵字.

優化上面的多值多域比對…searchkey,可以無需像我那樣設定searchkey來指定goodsName和keywords

我們可以在solr用戶端這樣用,直接上截圖

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

四: 配置中文分詞器 (其實這一步可以放在第二步之前,放到這裡也沒關系)

由于solr中沒有對于中文有很好的智能分詞器,是以需要我們手動配置中文分詞器,我使用的是ik分詞器.

我使用Ik-Analyzer5.5 将解壓後的唯一一個jar包複制到solr-home\wscore\lib中.

然後将Ik-Analyzer下的3個檔案ext.dic 和stopword.dic還有IKAnalyzer.xml也一起複制到Tomcat8\webapps\solr\WEB-INF\classes目錄中

Ik-Analyzer5.5中的檔案 各個檔案的作用
IKAnalyzer.xml 這裡主要是指定ext.dic和stopword.dic…,隻要這3個檔案在同一目錄下就不用修改這裡
ext.dic 是ik的自定義詞庫,我們可以把不當做關鍵字的詞,配置在這裡變成關鍵字…如:汝淉本身不是一個關鍵字,定義在這裡就是個關鍵字了.
stopword.dic 是ik的自定義停詞詞庫,意味着和ext.dic作用是相反的…比如女性是關鍵字,配到停詞庫中,分詞器就不會把女性當做關鍵字了

其中的ext.dic是ik的自定義詞庫,使用者可以進行自定義配置自己的詞庫…配置成功以後隻要再次啟動solr服務就生效了…

還有一步很重要:找到solr-home\core\conf中的managed-schema檔案…然後打開此檔案.加上這句話

<fieldType name="text_ik" class="solr.TextField">
    <analyzer type="index" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
    <analyzer type="query" class="org.wltea.analyzer.lucene.IKAnalyzer"/>
  </fieldType>
           

上截圖

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

下面是對含有中文的字段配上ik,當然要有分詞使用想法的字段,不能凡是中文字段的都配(沒這個必要),

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

到此分詞器就配置成功了.我們可以到Solr的操作界面去看看…啟動我本地tomcat即可.然後通路http://localhost:10003/solr/index.html進行測試(我的tomcat端口号設定成10003)

在用戶端界面,切換到自己的core下面的分詞類目中.-----Analysis—界面.就會出現如下界面

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

看到這步,就說明分詞效果配置成功了…

拓展分詞詞庫–自定義詞庫

除了solr自身的分詞詞庫以外,有些詞ik并不識别,但是我們需要用到,我們需要建立自己需要的新的關鍵字 比如我輸入"褲子男士", ik會分成褲子,男士這2個關鍵字,除此以外我還想要關鍵字 "褲子男"這個關鍵字,這時候就得用到拓展四庫之自定義詞庫.

接下來我們就配置自定義詞庫…找到Tomcat8\webapps\solr\WEB-INF下的classes這個檔案…還記得這個檔案是在前幾步就建立好了…裡面放了ext.dic 和stopword.dic還有IKAnalyzer.xml還有一個日志檔案.總共4個檔案…之前提過ext.dic就是拓展詞庫的配置檔案…在這個檔案中專門存放自定義的關鍵字-----上截圖更直覺

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

然後重新開機tomcat就能生效… 同理停詞詞庫也是這樣配置的…把ik識别的關鍵字配到stopword.dic就不會在當成關鍵字了.

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

值得注意的是:ext.dic和stopword.dic…這2個檔案的編碼 必須是utf-8無bom編碼格式,否則不生效.

到此支援中文智能分詞的分詞器就配置成功了.

五:增量和全量資料導入

增量:其實solr就是一個NOSQL,隻是裡面存儲的都是被索引好的資料,查詢的時候就查詢索引庫中的資料就行了,但是問題來了,資料裡面的資料更新了,被之前導入到solr中的資料卻還是原來的–也就是solr和資料庫的資料不同步,基于這個需求,就衍生出了同步被更新的資料到solr中…

增量原理::需要資料庫來一個updateTime字段,凡是資料修改就更新該字段為目前時間…然後用這個字段和上一次增量執行時間作比較…如果updateTime>大于上一次索引更新時間,就說明該資料修改過了,需要重新增量導入…索引更新時間,一會截圖給出…

全量:相對于增量全量是把所有需要的資料全部重新導入到solr中

操作如下:

1. 找到solr-home\wscore\conf下的data-config.xml檔案…這裡就是配置資料庫增量的相關配置.

2. 在增量導入資料的時候需要一個jar包…這個jar包是資料庫的驅動…将此jar包上傳到Tomcat8\webapps\solr\WEB-INF\lib下…啟動tomcat就可以了,在solr用戶端就能測試了…

直接上傳本地處理好的schema-manaed這個檔案和data-config.xml檔案.

這是schema-manaed中相關字段的配置..
<field name="goodsId" type="long" multiValued="false" indexed="true" stored="true"/>
  <field name="goodsName" type="text_ik" multiValued="false" indexed="true" stored="true"/>
  <field name="goodsSn" type="text_ik" multiValued="false" indexed="false" stored="true"/>
  <field name="keywords" type="text_ik" multiValued="false" indexed="true" stored="true"/>
  <field name="isOnSale" type="int" multiValued="false" indexed="true" stored="true"/>
  <field name="isDel" type="int" multiValued="false" indexed="true" stored="true"/>
  <field name="isNoShow" type="int" multiValued="false" indexed="true" stored="true"/> 
  <field name="isSoldOut" type="int" multiValued="false" indexed="true" stored="true"/>
  <field name="platType" type="int" multiValued="false" indexed="true" stored="true"/>
  <field name="shopPrice" type="double" multiValued="false" indexed="true" stored="true"/>
  <field name="marketPrice" type="double" multiValued="false" indexed="false" stored="true"/>
  <field name="originalImg" type="string" multiValued="false" indexed="false" stored="true"/>
  <field name="comments" type="long" multiValued="false" indexed="true" stored="true"/>
  <field name="soldNum" type="long" multiValued="false" indexed="true" stored="true"/>
  <field name="brandId" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="cateId" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="collectTimes" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="goodsColor" type="string" multiValued="false" indexed="false" stored="true"/>
  <field name="goodsWeight" type="double" multiValued="false" indexed="false" stored="true"/>
  <field name="iconImg" type="string" multiValued="false" indexed="false" stored="true"/>
  <field name="isComm" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="isHot" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="isReal" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="memberPrice" type="double" multiValued="false" indexed="false" stored="true"/>
  <field name="cashFenxiao" type="float" multiValued="false" indexed="false" stored="true"/>
  <field name="cashAgentLevel5" type="float" multiValued="false" indexed="false" stored="true"/>
  <field name="goodsThumb" type="string" multiValued="false" indexed="false" stored="true"/>
  <field name="goodsImg" type="string" multiValued="false" indexed="false" stored="true"/>
  <field name="preSale" type="int" multiValued="false" indexed="false" stored="true"/>
  <field name="platCode" type="string" multiValued="false" indexed="true" stored="true"/>
  <field name="searchkey" type="text_ik" multiValued="true" indexed="true" stored="false"/>
  
  <field name="text" type="text_ik" multiValued="true" indexed="true" stored="false"/>
  
  <copyField source="goodsId" dest="text"/>
  <copyField source="goodsName" dest="searchkey"/>
  <copyField source="goodsSn" dest="text"/>
  <copyField source="keywords" dest="searchkey"/>
  <copyField source="isOnSale" dest="text"/>
  <copyField source="isSoldOut" dest="text"/>
  <copyField source="isDel" dest="text"/>
  <copyField source="isNoShow" dest="text"/>
  <copyField source="platType" dest="text"/>
  <copyField source="shopPrice" dest="text"/>
  <copyField source="marketPrice" dest="text"/>
  <copyField source="originalImg" dest="text"/>
  <copyField source="comments" dest="text"/>
  <copyField source="soldNum" dest="text"/>
  <copyField source="brandId" dest="text"/>
  <copyField source="cateId" dest="text"/>
  <copyField source="collectTimes" dest="text"/>
  <copyField source="goodsColor" dest="text"/>
  <copyField source="goodsWeight" dest="text"/>
  <copyField source="iconImg" dest="text"/>
  <copyField source="isComm" dest="text"/>
  <copyField source="isHot" dest="text"/>
  <copyField source="isReal" dest="text"/>
  <copyField source="memberPrice" dest="text"/>
  <copyField source="cashFenxiao" dest="text"/>
  <copyField source="cashAgentLevel5" dest="text"/>
  <copyField source="goodsThumb" dest="text"/>
  <copyField source="goodsImg" dest="text"/>
  <copyField source="preSale" dest="text"/>
  <copyField source="platCode" dest="text"/>
           

data-config.xml 索引導入檔案

data-config.xml檔案..索引導入檔案....
<dataConfig>
    <dataSource 
				name="source"
	            type="JdbcDataSource" 
	            driver="com.mysql.jdbc.Driver" 
				url="jdbc:mysql://*********/?zeroDateTimeBehavior=convertToNull" 
				user="root" 
				password="*************"/>
    <document>
    這裡是全量的entity,name是這個entity的名字可以自定義.pk就是主鍵,具體看這個模闆就能了解.
    query是資料庫的sql,凡是被查出來的資料就會被導入進去..
			<entity name="full_import_data" dataSource="source" pk="goods_id" query="select goods_id ,
											goods_name,goods_sn ,keywords,convert(is_on_sale,UNSIGNED) as isOnSale,convert(is_delete,UNSIGNED)as isDel,
											convert(is_no_show,UNSIGNED)as isNoShow,is_sold_out ,convert(plat_type,UNSIGNED) as platType,shop_price, market_price,
											original_img ,comments,sold_num ,brand_id,cate_id,collect_times,goods_color,weight,icon_img,member_price,cash_fenxiao,
                                            cash_agent_level5,goods_thumb,square_img,CONVERT (pre_sale, UNSIGNED) AS preSale,CONVERT (is_real, UNSIGNED) AS isReal,
											CONVERT (is_hot, UNSIGNED) AS isHot,CONVERT (is_comm, UNSIGNED) AS isComm,plat_code
											from wsmall_goods.gss_goods where is_delete=0 ">
					 
			這裡    資料庫中字段名字     schema-managed中的字段名					 
            <field column="goods_id" name="goodsId" />
			<field column="goods_sn" name="goodsSn" />
			<field column="goods_name" name="goodsName" />
			<field column="keywords" name="keywords" />
			<field column="is_on_sale" name="isOnSale" />
			<field column="is_delete" name="isDel" />
			<field column="is_no_show" name="isNoShow" />
			<field column="is_sold_out" name="isSoldOut" />
			<field column="plat_type" name="platType" />
			<field column="shop_price" name="shopPrice" />
			<field column="market_price" name="marketPrice" />
			<field column="original_img" name="originalImg" />
			<field column="comments" name="comments" />
			<field column="sold_num" name="soldNum" />			
			<field column="brand_id" name="brandId" />	
			<field column="cate_id" name="cateId" />	
			<field column="collect_times" name="collectTimes" />	
			<field column="goods_color" name="goodsColor" />	
			<field column="weight" name="goodsWeight" />	
			<field column="icon_img" name="iconImg" />	
			<field column="is_comm" name="isComm" />	
			<field column="is_hot" name="isHot" />	
			<field column="is_real" name="isReal" />	
			<field column="member_price" name="memberPrice" />	
			<field column="cash_fenxiao" name="cashFenxiao" />	
			<field column="cash_agent_level5" name="cashAgentLevel5" />	
			<field column="goods_thumb" name="goodsThumb" />	
			<field column="square_img" name="goodsImg" />
            <field column="pre_sale" name="preSale" />		
			<field column="plat_code" name="platCode" />			
            </entity>
            
這裡是增量的entity...
deletedPkQuery:這裡是名額記那些被删除的記錄的主鍵id,因為這些記錄不需要導入到索引庫中..
deltaImportQuery:這個就是導入那些被修改未删除的記錄,,,未修改的資料不做操作,,還在索引庫中..
deltaQuery:這一步很重要:這裡是查詢出那些修改的資料,原理:需要資料庫來一個updateTime字段,
             凡是資料修改就更新該字段為目前時間..然後用這個字段和上一次增量執行時間作比較..如果updateTime>大于
             上一次索引更新時間,,就說明該資料修改過了,,需要重新增量導入...索引更新時間,一會截圖給出..
			<entity name="delta_import_data" dataSource="source" pk="goods_id" 	
					    deletedPkQuery="select goods_id FROM wsmall_goods.gss_goods where is_delete=1" 
                        deltaImportQuery="select goods_id ,
											goods_name,goods_sn ,keywords,convert(is_on_sale,UNSIGNED) as isOnSale,convert(is_delete,UNSIGNED)as isDel,
											convert(is_no_show,UNSIGNED)as isNoShow,is_sold_out ,convert(plat_type,UNSIGNED) as platType,shop_price, market_price,
											original_img ,comments,sold_num ,brand_id,cate_id,collect_times,goods_color,weight,icon_img,member_price,cash_fenxiao,
                                            cash_agent_level5,goods_thumb,square_img,CONVERT (pre_sale, UNSIGNED) AS preSale,CONVERT (is_real, UNSIGNED) AS isReal,
											CONVERT (is_hot, UNSIGNED) AS isHot,CONVERT (is_comm, UNSIGNED) AS isComm,plat_code
											from wsmall_goods.gss_goods where is_delete=0 and goods_id='${dataimporter.delta.goods_id}'" 
						deltaQuery="select goods_id FROM wsmall_goods.gss_goods where from_unixtime(update_time,'%Y-%m-%d %H:%i:%m') > '${dataimporter.last_index_time}'">
					 
			<field column="goods_id" name="goodsId" />
			<field column="goods_sn" name="goodsSn" />
			<field column="goods_name" name="goodsName" />
			<field column="keywords" name="keywords" />
			<field column="is_on_sale" name="isOnSale" />
			<field column="is_delete" name="isDel" />
			<field column="is_no_show" name="isNoShow" />
			<field column="is_sold_out" name="isSoldOut" />
			<field column="plat_type" name="platType" />
			<field column="shop_price" name="shopPrice" />
			<field column="market_price" name="marketPrice" />
			<field column="original_img" name="originalImg" />
			<field column="comments" name="comments" />
			<field column="sold_num" name="soldNum" />			
			<field column="brand_id" name="brandId" />	
			<field column="cate_id" name="cateId" />	
			<field column="collect_times" name="collectTimes" />	
			<field column="goods_color" name="goodsColor" />	
			<field column="weight" name="goodsWeight" />	
			<field column="icon_img" name="iconImg" />	
			<field column="is_comm" name="isComm" />	
			<field column="is_hot" name="isHot" />	
			<field column="is_real" name="isReal" />	
			<field column="member_price" name="memberPrice" />	
			<field column="cash_fenxiao" name="cashFenxiao" />	
			<field column="cash_agent_level5" name="cashAgentLevel5" />	
			<field column="goods_thumb" name="goodsThumb" />	
			<field column="square_img" name="goodsImg" />
            <field column="pre_sale" name="preSale" />
			<field column="plat_code" name="platCode" />
            </entity>
    </document>

</dataConfig>
           

上一次索引時間記錄的檔案在solr-home/core/conf中的 dataimport.properties檔案…打開此檔案

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

配置好了,就需要我們執行了…看頁面截圖

全量導入用戶端操作

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

增量的導入操作,如圖

Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

我增量和全量中遇到的大BUG

BUG1.solr對于資料庫類型為tinyint類型的字段,導入進索引庫中會變成Boolean類型的資料…這樣導緻資料失去原有數值表達的含義,建立的solr索引的也沒法使用…如何規避

解決方案

1.更改資料表,将tinyint類型轉化為int類型。(不推薦)

2.在DIH擷取MySQL資料集通過sql查詢時進行類型轉換,将查詢結果中tinyint類型轉換為int類型

MySQL中使用CONVERT(表達式,類型)函數進行類型轉換

select convert(is_on_sale,UNSIGNED) as isOnSale,convert(is_delete,UNSIGNED)as isDel,
               convert(is_no_show,UNSIGNED)as isNoShow
               from  .....
           

BUG2.如果把增量和全量放在一個entity中,全量能執行,增量執行不了,sql其他都是正确的…

解決方案:把分成2個entity,增量一個 全量一個,執行的時候選擇指定的…

就像我上面傳的data-config.xml中的2個entity一樣,就可以執行了,隻要從一個entity分離出來就行了…具體原因不詳…

六:定時增量,定時全量

很多人希望定時增量,具體有2中方式…

第一種:solr自帶的,定時器,需要配置就行…因為我這個操作過,但都沒有成功,是以沒法貼出來…(不推薦),不推薦的原因不是自己沒有折騰出來,而是會增加solr的性能,一般用的多的人都會選擇代碼中的定時器來完成這個…

第二種:代碼中的定時器來完成…推薦…我寫了2中定時器…選擇自己合适的就行…

pom.xml中需要的依賴;

<!-- solr -->
        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>6.6.2</version>
        </dependency>
<!-- quartz 定時器-->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>${quartz.version}</version>
            </dependency>
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz-jobs</artifactId>
                <version>${quartz.version}</version>
            </dependency>
           

上增量定時器代碼: 這個使用的是Spring的定時器

http://localhost:10005/solr/wscore2/dataimport?command=delta-import&entity=delta_import_evalu&clean=false&commit=true 這個位址就是增量的位址,隻要通路這個位址就能執行增量操作…是以定時器的任務就是去通路這個位址就行…

首先在web.xml中加上這句話…來監聽這個類…是以本應該那個位址(就是增量通路位址)配在配置檔案中的,但是,web.xml會首先加載,再去加載配置檔案,…是以就隻能放在這個被監聽的類中了…

<listener>
		<listener-class>com.wsmall.solr.quartz.StartupListener4analysisReport</listener-class>
	</listener>
           

上代碼:

package com.wsmall.solr.quartz;

import com.wsmall.solr.common.util.HttpClientUtil;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by RuGuo on 2018/1/31.
 */
public class StartupListener4analysisReport implements ServletContextListener{

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(StartupListener4analysisReport.class);

    //@Value("${solr.data.url}")  
    private  static final String       solrDataUrl = "http://localhost:10005/solr/wscore/dataimport";
    //@Value("${solr.delta.param.url}")
    private  static final String      solrDeltaUrl ="command=full-import&entity=full_import_data&clean=true&commit=true" ;
    //evalu & media                                           "http://localhost:10005/solr/wscore2/dataimport"
    private  static final String      solrEvaluMediaDataUrl = "http://localhost:10005/solr/wscore2/dataimport";
    private  static final String      solrDeltaEvaluUrl = "command=delta-import&entity=delta_import_evalu&clean=false&commit=true";

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        servletContextEvent.getServletContext().log("啟動線程池");
        servletContextEvent.getServletContext().log("啟動定時器");

        //執行goods增量導入
        Runnable runnableDelta = new Runnable() {
            public void run() {
                // task to run goes here
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String  time=simpleDateFormat.format(new Date());
                logger.warn("solr定時增量執行循環**delta**"+ time + "**delta**");
                String responseJson = HttpClientUtil.get(solrDataUrl,solrDeltaUrl);
            }
        };
        ScheduledThreadPoolExecutor service = new ScheduledThreadPoolExecutor(5);
        //ScheduledExecutorService service = Executors
                //.newSingleThreadScheduledExecutor();
                //5分鐘執行一次增量
        service.scheduleAtFixedRate(runnableDelta, 0, 5, TimeUnit.MINUTES);
        //service.scheduleAtFixedRate(runnableFull, 0, 7, TimeUnit.MINUTES);




        //執行evalu增量導入
        Runnable runnableDeltaEvalu = new Runnable() {
            public void run() {
                // task to run goes here
                logger.warn("solr定時Evalu增量執行循環**Evalu**"+"**delta**");
                String responseJson = HttpClientUtil.get(solrEvaluMediaDataUrl,solrDeltaEvaluUrl);
            }
        };
        ScheduledThreadPoolExecutor serviceEvalu = new ScheduledThreadPoolExecutor(5);
        serviceEvalu.scheduleAtFixedRate(runnableDeltaEvalu, 0, 7, TimeUnit.MINUTES);

    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        servletContextEvent.getServletContext().log("定時器銷毀");
    }
}

           

全量導入代碼…全量采用的是quartz定時器 上代碼

package com.wsmall.solr.quartz;

import com.wsmall.solr.common.util.HttpClientUtil;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by RuGuo on 2018/1/25.
 */
public class QuartzFullImport {

    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(QuartzFullImport.class);

    @Value("${solr.data.url}")
    private String              solrDataUrl;//同樣都是通路位址,,,這個配在配置檔案中,,友善管理
    @Value("${solr.full.param.url}")
    private String              solrFullUrl;
配置檔案中的:
solr.data.url=http://localhost:10005/solr/wscore/dataimport
solr.full.param.url=command=full-import&entity=full_import_data&clean=true&commit=true
    /**
     * 定時全量導入
     */
    public void work()
    {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time =simpleDateFormat.format(new Date());
        logger.warn("solr定時全量執行循環//"+ time+"/");
        String responseJson = HttpClientUtil.get(solrDataUrl,solrFullUrl);
    }
}

           

quartz的配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    http://www.springframework.org/schema/task
    http://www.springframework.org/schema/task/spring-task-3.0.xsd">

    <!-- 線程執行器配置,用于任務注冊 -->
    <bean id="executor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="500" />
    </bean>

    <!-- 任務對象 -->
    <bean name="quartzFullImport" class="com.wsmall.solr.quartz.QuartzFullImport" />
    <bean name="quartzFullEvaluImport" class="com.wsmall.solr.quartz.QuartzFullEvaluImport" />

    <!-- 定時任務 -->
    
    <!-- ============= 排程業務=============  -->
    <bean id="quartzFullImportTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="quartzFullImport"></property>
        <!-- 排程的方法名 -->
        <property name="targetMethod" value="work"></property>
        <!-- 如果前一個任務還沒有結束第二個任務不會啟動 false -->
        <property name="concurrent" value="true"></property>
    </bean>

    <!-- ============= 排程業務=============  -->
    <bean id="quartzFullEvaluImportTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="quartzFullEvaluImport"></property>
        <!-- 排程的方法名 -->
        <property name="targetMethod" value="importFullEvalu"></property>
        <!-- 如果前一個任務還沒有結束第二個任務不會啟動 false -->
        <property name="concurrent" value="true"></property>
    </bean>
<!--//-->
    <!-- 定時任務觸發器 -->
    <bean id="quartzFullImportTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="quartzFullImportTask"/>
        <!-- 0 30 14 ? * TUE表示每個星期一淩晨3點 MON,TUE,WED,THU,FRI,SAT,SUN-->
        <property name="cronExpression" value="0 1 2 ? * *"></property>
    </bean>

    <bean id="quartzFullEvaluImportTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="quartzFullEvaluImportTask"/>
        <!-- 0 30 10 ? * * 每天早上10點30分觸發-->
        <property name="cronExpression" value="0 30 10 ? * *"></property>
    </bean>

    <!-- ============= 排程工廠 =============  -->
    <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref local="quartzFullImportTrigger" />
                <ref local="quartzFullEvaluImportTrigger" />
            </list>
        </property>
    </bean>

</beans>
           

HttpClientUtil 工具類

package com.wsmall.solr.common.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

/**
 * Created by pan on 12/01/2017.
 */
public class HttpClientUtil {

    private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);

    public static String postWithJson(String url, String jsonValues) {

        HttpClient httpClient = HttpClientBuilder.create().build();

        try {
            HttpPost request = new HttpPost(url);
            StringEntity params = new StringEntity(jsonValues);
            request.addHeader("content-type", "application/json");
            request.setEntity(params);
            HttpResponse response = httpClient.execute(request);
            return parseResponse(response);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }

    }

    /**
     * // * 向指定url發送get方法的請求 // * // * @param url 發送請求的url // * @param param 請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * // * @return result 所代表遠端資源的響應結果 //
     */
    public static String get(String url, String param) {
        // 傳回結果
        String result = "";
        BufferedReader in;
        in = null;
        try {
            // url
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打開和URL之間的連接配接
            URLConnection connection = realUrl.openConnection();
            // 設定通用的請求屬性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立實際的連接配接
            connection.connect();
            // 擷取所有響應頭字段
            // Map<String, List<String>> map = connection.getHeaderFields();

            // 周遊所有的響應頭字段
            /*
             * for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); }
             */
            // 定義 BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        // 使用finally塊來關閉輸入流
        finally {
            close(in);
        }

        // 如果外部接口傳回為空,傳回其他錯誤原因
        if (StringUtils.isBlank(result)) {
            result = "{\"result\":\"other error\"}";
        }

        return result;
    }

    /**
     * 将伺服器傳回的資料包裝成為String
     * 
     * @param response
     * @return
     * @throws IOException
     */
    private static String parseResponse(HttpResponse response) throws IOException {

        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
            logger.error("Method failed:" + response.getStatusLine());
        }

        // Read the response body
        return EntityUtils.toString(response.getEntity());
    }

    /**
     * @param closeable
     */
    private static void close(Closeable... closeable) {
        for (Closeable c : closeable) {
            try {
                if (c != null) {
                    c.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

           
Solr6.6.2-solr部署+solr增量,全量(定時)+分詞器(拓展詞庫)+項目代碼(查詢代碼等等)                                     solr之搜尋引擎五:增量和全量資料導入

solr查詢接口…上代碼:

model ------------GoodsVO.java 隻截取一部分,有點長…注意value,一會提他的作用

package com.wsmall.solr.pojo.goods.vo;


import org.apache.solr.client.solrj.beans.Field;

import java.io.Serializable;
import java.util.Date;

/**
 * Created by RuGuo on 2017/12/7.
 */
public class GoodsVo implements Serializable{
    //@Field("id")
    //private     String                  id;             //索引主鍵
    @Field("goodsId")
    private     Long                    goodsId;        //商品主鍵
    @Field("goodsSn")
   
    public Long getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Long goodsId) {
        this.goodsId = goodsId;
    }

    public String getGoodsSn() {
        return goodsSn;
    }

    public void setGoodsSn(String goodsSn) {
        this.goodsSn = goodsSn;
    }

    }

           

query的代碼

solr.url=http://localhost:10005/solr/wscore
 @Override
    public PageRecords<List<GoodsVo>> query(GoodsQueryParam goodsParam) {
        PageRecords<List<GoodsVo>> pageRecords = PageRecords.newByExisted(goodsParam);
        try {
            SolrClient solr = new HttpSolrClient.Builder(solrUrl).build();
            //HttpSolrClient httpSolrClient = SolrUtils.connect();
            SolrQuery query = new SolrQuery();
            //查詢條件
            if (StringUtils.isBlank(goodsParam.getSearchkey())) {
                query.setQuery("*:*");
            } else {
                query.setQuery(goodsParam.getSearchkey());
            }
            //設定預設搜尋域
            //query.set("df", "keyWords");
            if (null != goodsParam.getIsOnSale()){
                query.addFilterQuery("isOnSale:"+goodsParam.getIsOnSale());
            }
            if(null != goodsParam.getIsNoShow()){
                query.addFilterQuery("isNoShow:"+goodsParam.getIsNoShow());
            }
            if(null != goodsParam.getPlatType()){
                query.addFilterQuery("platType:"+goodsParam.getPlatType());
            }
            if(null != goodsParam.getIsSoldOut()){
                query.addFilterQuery("isSoldOut:"+goodsParam.getIsSoldOut());
            }
            if(null != goodsParam.getIsDel()){
                query.addFilterQuery("isDel:"+goodsParam.getIsDel());
            }else{
                query.addFilterQuery("isDel:0");
            }
            //排序
            if(null != goodsParam.getOrderBy()){
                if("shopPrice".equals(goodsParam.getOrderBy())){
                    query.set("sort", "shopPrice "+goodsParam.getSortCat());
                }
                if("soldNum".equals(goodsParam.getOrderBy())){
                    query.set("sort", "soldNum "+goodsParam.getSortCat());
                }
                if("comments".equals(goodsParam.getOrderBy())){
                    query.set("sort", "comments "+goodsParam.getSortCat());
                }
            }

            //分頁開始頁數
            query.setStart(goodsParam.getBegin());
            //設定傳回記錄數,預設為10條
            query.setRows(goodsParam.getRows());

            QueryResponse response = solr.query(query);
            SolrDocumentList list = response.getResults();
            Long recordCount = list.getNumFound();
            //Long recordSlipt = 0L;
            List<GoodsVo> goodsVoList = null;
            if(recordCount!=0){
            //還記得model中的字段是為什麼有value注解嗎?
            //作用就展現在這裡了,,轉換需要到這個注解,,
                 goodsVoList = response.getBeans(GoodsVo.class);
                pageRecords.setRecords(goodsVoList);
                pageRecords.setTotalRows(Integer.parseInt(recordCount.toString()));
                return  pageRecords;
            }
        } catch (SolrServerException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
           

分頁工具

package com.wsmall.solr.common.db;

import com.wsmall.solr.common.Page;

/**
 * Created by pan on 15/11/2016.
 */
public class PageRecords<T> extends Page {

    private T records; // 結果集

    public PageRecords() {
    }

    //    public PageRecords(int currentPage, int rows) {
    //        super(currentPage, rows);
    //    }

    public static <A> PageRecords<A> newByExisted(Page formPage) {
        PageRecords<A> page = new PageRecords();
        page.setCurrentPage(formPage.getCurrentPage());
        page.setRows(formPage.getRows());
        page.setTotalRows(formPage.getTotalRows());
        page.setDisabledCountPage(formPage.isDisabledCountPage());
        page.setDisabledPage(formPage.isDisabledPage());
        return page;
    }

    public PageRecords(int currentPage, int rows, int totalRows) {
        super(currentPage, rows, totalRows);
    }

    public T getRecords() {
        return records;
    }

    public void setRecords(T records) {
        this.records = records;
    }

    /**
     * 擷取統計條數,如若沒有禁用統計功能
     * @param queryable
     */
    public void queryByPage(PageQueryable<T> queryable) {

        // 若禁用分頁,或者分頁查詢就跳過count方法
        if (!isDisabledPage() && !isDisabledCountPage()) {
            // 查詢并設定分頁
            this.setTotalRows(queryable.count());
            // 無資料就無須繼續往下查詢query方法
            if (this.getRows() < 1) {
                return;
            }
        }
        T t = queryable.query();
        setRecords(t);
    }

}

           

page類

package com.wsmall.solr.common;

/**
 * Created by zgj on 2015-03-03.
 */
public class Page {

    /**
     * 查詢記錄從第幾條開始
     */
    private int     begin             = 0;
    /**
     * 目前頁碼
     */
    private int     currentPage       = 1;
    /**
     * 每頁記錄數
     */
    private int     rows              = 10;
    /**
     * 總頁數
     */
    private int     totalPages        = 0;
    /**
     * 總記錄數
     */
    private int     totalRows         = 0;

    private boolean disabledCountPage = Boolean.TRUE;

    private boolean disabledPage;

    public Page() {
    }

    public Page(int currentPage, int rows) {
        this.currentPage = currentPage;
        this.rows = rows;
        this.begin = (currentPage - 1) * rows;
    }

    public Page(int currentPage, int rows, int totalRows) {
        this(currentPage, rows);
        setTotalRows(totalRows);
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(Integer currentPage) {

        if (currentPage != null) {
            this.currentPage = Math.max(currentPage, 1);
            this.begin = (this.currentPage - 1) * rows;
        }
    }

    public int getRows() {
        return rows;
    }

    public void setRows(Integer rows) {
        if (rows != null) {
            this.rows = rows;
        }
        setCurrentPage(this.currentPage);
    }

    public int getTotalPages() {
        return totalPages;
    }

    public void setTotalPages(Integer totalPages) {
        if (totalPages != null) {
            this.totalPages = totalPages;
            if (rows > 0 && totalPages > 0) {
                this.totalRows = rows * totalPages;
            }
        }
    }

    public int getTotalRows() {
        return totalRows;
    }

    public void setTotalRows(Integer totalRows) {
        if (totalRows != null) {
            this.totalRows = totalRows;
            if (rows > 0 && totalRows > 0) {
                this.totalPages = (int) (Math.ceil((double) totalRows / rows));
            }
        }
    }

    public int getBegin() {
        return begin;
    }

    public void setBegin(Integer begin) {
        if (begin != null) {
            this.begin = begin;
        }
    }

    public boolean isDisabledPage() {
        return disabledPage;
    }

    public void setDisabledPage(boolean disabledPage) {
        this.disabledPage = disabledPage;
    }

    public boolean isDisabledCountPage() {
        return disabledCountPage;
    }

    public void setDisabledCountPage(boolean disabledCountPage) {
        this.disabledCountPage = disabledCountPage;
    }

}

           

如果轉載,請說明出處.謝謝.

到此,我能接觸的業務就結束了…都是個人自己寫的,肯定會有一些不當的地方,還希望海涵,當然指出來多交流…

繼續閱讀