天天看點

Liferay6.2:Search And Indexing - 搜尋和索引Search And Indexing - 搜尋和索引

文章目錄

  • Search And Indexing - 搜尋和索引
    • Implementing Search and Indexing - 實作搜尋與索引
    • Creating and Registering an Indexer Class - 建立并注冊索引類
    • Implementing Indexing at the Service Layer - 在服務層實作索引
    • Providing a Search Mechanism - 提供查詢機制

Search And Indexing - 搜尋和索引

假設你正在開發一個資料驅動的應用程式。你預計你的應用程式将存儲大量資料庫記錄。你可以向使用者提供一些預制查詢,但是你無法讀懂他們查找資料的各種方式。這種情況下你可以為自定義實體(entity)建立索引(index)以便它們可以搜尋索引。Liferay的搜尋和索引功能由Java搜尋架構Lucene提供。 Lucene的工作方式是将可搜尋的實體(entity)轉換為文檔(document)。 Lucene檔案不是普通意義上的檔案。相反,它們是與可搜尋實體相對應的自定義對象。搜尋Lucene索引時,将傳回一個hits對象,其中包含指針(pointer),指針指向與搜尋查詢所比對的文檔。使用索引搜尋一般會比搜尋資料庫中的實體更快。如果索引文檔包含您感興趣的資料,則可以避免完全進行資料庫查詢。

例如,假如你要檢索在特定字段中具有特定值的自定義實體。 如果沒有索引(index),則必須從資料庫中檢索所有自定義實體,并檢查每個實體的特定字段以擷取特定值。 使用索引,你隻需要在索引文檔的特定字段中搜尋特定值。 在索引中獲得比對的文檔(document)後,你可以檢索(retrieve)所比對文檔的任何其他字段的值。 如果需要,可以運作資料庫查詢以檢索與索引文檔對應的實體。 這樣的資料庫查詢會比檢索所有自定義實體的查詢開銷小得多。 在本教程的這一部分中,你将探索Liferay的搜尋和索引API,并學習如何在你的應用程式中使用它們。

Implementing Search and Indexing - 實作搜尋與索引

Liferay的搜尋和索引功能由Apache Lucene提供,這是一個基于Java的搜尋庫。 要實作實體的搜尋和索引功能,你需要執行以下三個步驟:

  1. 在portlet項目中建立一個*Indexer類,并在項目的liferay-portlet.xml檔案中注冊該類。
  2. 更新實體的服務層(service layer),以便在增删改實體時同步更新索引。
  3. 提供執行搜尋的機制。 例如,你可以在portlet項目中建立一個用于輸入搜尋查詢的JSP以及一個用于顯示搜尋結果的JSP。 或者可以簡單地配置Liferay自帶搜尋功能的portlet來搜尋實體。

你将在以下章節中探索每個步驟。 在繼續學習之前,請確定你熟悉以下搜尋和索引的術語(terminology)。

  • doucment 文檔 :一個搜尋索引(search index)包含一系列文檔(doucment)的集合。這些文檔不是普通意義上的文檔。它們表示已儲存到資料庫中的實體對應的Java對象。
  • fields 字段 : 文檔包含字段(field)及其值的集合。文檔的字段表示每個文檔的中繼資料(metadata)。一些典型字段比如title, content, description, create date, modified date, tags 等等。一個文檔的字段不一定必須與關聯實體的屬性一一對應。
  • term 術語:字段可以是單值(single-valued)或多值(multi-valued)的。單值字段隻能有一個術語(term)。多值字段可以具有多個術語(term)。術語(term)是可以被搜尋的單個非空值。
  • phrase 短語:短語(phrase) 是由空格分隔的一系列術語(term)。在搜尋中使用短語(phrase) 作為術語(term)的唯一方法是用雙引号(“)括起來。
  • hit 命中:搜尋結果是命中(hit)集合。命中(Hit)是指向與搜尋查詢比對的文檔(document)的指針。

Creating and Registering an Indexer Class - 建立并注冊索引類

Indexer類負責建立代表自定義實體的Lucene文檔。 建立索引器類時,需要确定文檔應包含哪些字段以及應如何為字段指派。 建議您的索引器類擴充com.liferay.portal.kernel.search.BaseIndexer或至少實作com.liferay.portal.kernel.search.Indexer。 這樣的話你可以将實體與門戶實體(asset) 內建在一起,并允許你的實體使用現有的門戶架構(portal framework),例分片搜尋(faceted search)。 如果要對實體進行asset啟用,則為它們建立索引器(indexer)是必須的步驟。 建立索引器類時,可以使用與Liferay asset對應的索引器類作為示例。 這些包括BlogsIndexer,JournalArticleIndexer,WikiPageIndexer等等。可以參考 Search and Indexing 學習路徑以擷取一個示例。

如果你的索引器類擴充了com.liferay.portal.kernel.search.BaseIndexer(如推薦),則需要覆寫或提供以下方法的實作:

  • public String[] getClassNames()
  • public String getPortletId()
  • protected void doDelete(Object obj)
  • protected Document doGetDocument(Object obj)
  • protected Summary doGetSummary(Document document, Locale locale, String snippet, PortletURL portletURL)
  • protected void doReindex(Object obj)
  • protected void doReindex(String className, long classPK)
  • protected void doReindex(String[] ids)
  • protected String getPortletId(SearchContext searchContext)

建議定義public static final String[] CLASS_NAMES常量和public static final String PORTLET_ID常量。 這些常量應包含實體類的名稱和關聯的portlet的名稱。 Liferay的Indexer類遵循此約定。 在plugin中使用這些常量可確定你在索引代碼中始終使用正确的類名和portlet ID。 上面引用的search and indexing learning path 解釋了如何實作必須的方法。 Liferay自帶的Indexer類也提供了很好的示例。

一旦為實體編寫了索引器類,就需要在Liferay中注冊這個類。 為此,打開portlet項目的docroot/WEB-INF/liferay-portlet.xml檔案并添加如下條目:

<indexer-class>[fully qualified class name of the indexer class]</indexer-class>
           

例如:

<indexer-class>com.liferay.portlet.blogs.util.BlogsIndexer</indexer-class>
           

如果你正在使用新生成的liferay-portlet.xml檔案,則應在元素内的元素下方添加該條目,該元素對應于你要添加索引器的portlet。 如果你正在使用包含大量條目的liferay-portlet.xml檔案,請參閱DTD 以确定應添加索引器類條目的位置。 在将元素添加到liferay-portlet.xml之後重新部署項目,以便Liferay為portlet注冊索引器。

如果你想檢視門戶(portal)索引器系統資料庫的内容,可以從Liferay的腳本控制台執行以下Groovy腳本。 此腳本使用Liferay的IndexerRegistryUtil類來檢索已注冊的索引器清單。 然後,它為每個索引器列印關聯的portlet ID和類名。 要通路腳本控制台,請轉至Admin → Control Panel,然後單擊Server Administration,然後單擊Script。 選擇Groovy作為語言,然後輸入以下腳本。

檢視腳本,單擊Execute

import com.liferay.portal.kernel.search.IndexerRegistryUtil;
import com.liferay.portal.kernel.search.Indexer;

import java.util.List;

List<Indexer> indexers = IndexerRegistryUtil.getIndexers();

for (Indexer indexer : indexers) {
    System.out.println("portletId: " + indexer.getPortletId());

    String[] classnames = indexer.getClassNames();

    for (String classname : classnames) {
        System.out.println("classname: " + classname);
    }

    System.out.println();
}
           

檢查控制台中的portlet ID及其關聯的索引器類名稱清單。 驗證你在liferay-portlet.xml中注冊的portlet和索引器類是否出現在清單中。

Implementing Indexing at the Service Layer - 在服務層實作索引

如果你正在建立資料驅動的應用程式,那麼你可能已經編寫了用于添加,更新和删除實體(entity)的代碼。如果你希望與Liferay的Lucene索引中與實體對應的文檔(document)與實體同步,則需要讓索引器為任何添加或更新的實體重建索引。删除實體時,需要從索引中删除相應的文檔。要擷取索引器類的執行個體,請使用Liferay的IndexerRegistryUtil類。該類包括getIndexer方法以及nullSafeGetIndexer方法。這兩種方法都可以采用類參數(例如MyEntity.class)或表示類名的字元串(例如MyEntity)。如果使用getIndexer并且在系統資料庫中沒有索引器與參數比對,則傳回null。但是,如果使用nullSafeGetIndexer并且也沒有索引器與參數比對,則傳回虛拟索引器(dummy indexer)。傳回一個虛拟索引器比傳回null更安全,因為傳回null可能會抛出導緻portlet無法使用的異常。

獲得與實體(entity)對應的索引器(indexer)後,需要調用适當的索引操作。 每當添加新實體時,都應将相應的文檔(doucment)添加到索引(index)中。 每當更新現有實體時,都應更新相應的文檔。 這兩個任務(索引(index)和重建索引(reindex))都可以通過調用索引器的reindex方法來完成。 此方法已重載。 你可以提供要建立索引的對象作為參數,也可以提供實體的類名和主鍵。 删除實體時,應從索引中删除其相應的文檔。 你可以通過調用索引器的delete方法來完成此操作。 與reindex類似,delete也被重載,并且可以将對象或實體的類名和主鍵作為參數。

如果你正在portlet項目的服務層中使用Service Builder,那麼你應該知道将自定義業務邏輯添加到實體的[Entity]LocalServiceImpl類中。 如果你已将應用程式與Liferay的權限系統內建,則可能已經擁有了add[Entity],update[Entity]和delete[Entity]方法,這些方法調用resourceLocalService的各種方法來添加,更新和删除實體資源。 如果你已為asset啟用了實體,則你已使用assetEntryLocalService和assetLinkLocalService來添加,更新和删除實體的asset條目和asset連結(相關連的asset)。 你将使用類似上面的過程在索引中添加和删除實體。 為此,請在add [Entity]和update{Entity]中添加以下行代碼:

Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer([Entity].class);

indexer.reindex(entry);
           

在delete[Entity]方法中添加以下代碼:

Indexer indexer = IndexerRegistryUtil.nullSafeGetIndexer([Entity].class);

indexer.delete(entry);
           

當然,在上述兩個代碼片段中,将[Entity]替換為你實體的名稱。 儲存您的[Entity] LocalServiceImpl.java檔案中并重新部署portlet項目。

使用portlet添加和更新某些實體以測試索引器。 如果它正常工作,你的索引器應該會為Liferay的索引添加文檔(document),以添加或更新每個實體。 預設情況下,Liferay的索引資料存儲在[Liferay Home]/ data/lucene檔案夾中。 此檔案夾包含的子檔案夾對應着每個門戶執行個體。 例如,如果你的預設門戶執行個體的company ID為10154,則[Liferay Home]/data/lucene中應該有一個名為10154的檔案夾。 10154檔案夾的内容為二進制格式; 它可以使用第三方工具(如Luke)來浏覽Lucene索引。

Luke是一個開發工具,允許你浏覽和修改現有的Lucene索引。 它是個免費開源軟體。 你可以在此處通過Google代碼擷取該代碼:https://code.google.com/p/luke。 下載下傳.jar檔案。 要啟動Luke,請打開指令行,導航到包含.jar檔案的目錄,然後運作以下指令:

java -jar lukeall-3.5.0.jar
           

如果你使用的是較新版本的Luke,請将以上版本替換為您的版本。 一旦Luke運作,你需要檢查Liferay的Lucene索引以確定你的實體(entity)已被編入索引(index)。 單擊File →打開Lucene Index,然後輸入要打開其索引的門戶網站執行個體的檔案夾的路徑。 例如,如果你的門戶網站執行個體的公司是10154,請輸入以下路徑:

[Liferay Home]/data/lucene/10154
           

檢查以下項以確定可以打開索引并且在浏覽時不會意外修改索引:

  • 以Read-Only模式打開
  • 強制unlock,如果locked

然後單擊确定(OK)。如果單擊Luke的“Document”頁籤,則可以按文檔編号浏覽所有索引文檔。 如果單擊Luke的“Search”頁籤,則可以輸入搜尋查詢。 單擊Search,Luke将顯示所有比對文檔的清單。 使用Luke檢查你通過portlet添加的實體是否出現在Liferay的Lucene索引中。 如果您需要學習Lucene的搜尋查詢文法,請參閱Lucene的documentation。 注意:Liferay 6.2使用Lucene 3.5.0。

Providing a Search Mechanism - 提供查詢機制

現在你的索引器已經注冊,并且每當添加,更新或删除實體時都會更新索引,現在是時候建立搜尋機制了。 允許使用者搜尋實體的最簡單方法是通過Liferay的搜尋portlet。 預設情況下,Liferay的搜尋portlet僅允許搜尋開箱即用的Liferay asset:

  • Users
  • Bookmarks
  • Bookmark folders
  • Blog posts
  • Documents and Media files
  • Documents and Media folders
  • Web Content files
  • Web Content folders
  • Message Board Messages
  • Wiki pages

可以輕松配置搜尋portlet以搜尋其他asset類型。 為此,請将search portlet添加到頁面并打開“配置(Configuration)”視窗。 在“設定(Setup)”頁籤上,單擊“進階(Advanced)”以顯示“搜尋配置(Search Configuration)”文本區域。 在JSON配置中搜尋以下代碼塊:

"values": [
    "com.liferay.portal.model.User",
    "com.liferay.portlet.bookmarks.model.BookmarksEntry",
    "com.liferay.portlet.bookmarks.model.BookmarksFolder",
    "com.liferay.portlet.blogs.model.BlogsEntry",
    "com.liferay.portlet.documentlibrary.model.DLFileEntry",
    "com.liferay.portlet.documentlibrary.model.DLFolder",
    "com.liferay.portlet.journal.model.JournalArticle",
    "com.liferay.portlet.journal.model.JournalFolder",
    "com.liferay.portlet.messageboards.model.MBMessage",
    "com.liferay.portlet.wiki.model.WikiPage"
],
           

把你的實體添加到清單中:

"values": [
    "com.liferay.portal.model.User",
    "com.liferay.portlet.bookmarks.model.BookmarksEntry",
    "com.liferay.portlet.bookmarks.model.BookmarksFolder",
    "com.liferay.portlet.blogs.model.BlogsEntry",
    "com.liferay.portlet.documentlibrary.model.DLFileEntry",
    "com.liferay.portlet.documentlibrary.model.DLFolder",
    "com.liferay.portlet.journal.model.JournalArticle",
    "com.liferay.portlet.journal.model.JournalFolder",
    "com.liferay.portlet.messageboards.model.MBMessage",
    "com.liferay.portlet.wiki.model.WikiPage",
    "your.package.path.YourEntity"
],
           

單擊“儲存(Save)” 你可以通過這種方式向Search portlet的配置添加任意數量的實體。 有關Search portlet的JSON配置的更多資訊,請參閱Search portlet的文檔: Searching for Content in Liferay。

但是,你也可以不使用Liferay的Search portlet。 在本節中,你将學習如何使用Liferay的API在你自己的portlet中建立搜尋機制。 你可以建立一個用于輸入搜尋查詢的JSP和另一個用于顯示搜尋結果的JSP。 在portlet中實作搜尋和索引時,以下Liferay類很重要:

  • com.liferay.portal.kernel.search.SearchContext
  • com.liferay.portal.kernel.search.SearchContextFactory
  • com.liferay.portal.kernel.search.Indexer
  • com.liferay.portal.kernel.search.IndexerRegistryUtil
  • com.liferay.portal.kernel.search.BaseIndexer
  • com.liferay.portal.kernel.search.SearchEngineUtil
  • com.liferay.portal.kernel.search.Hits
  • com.liferay.portal.kernel.search.Document

要在Liferay中執行搜尋查詢,你需要一個SearchContext對象。 search context提供了諸如要搜尋的company執行個體,調用搜尋的使用者,local設定,時區等詳細資訊。由于此類具有多種上下文屬性需要處理,是以擷取執行個體的最有效方法是調用SearchContextFactory的getInstance(HttpServletRequest request)方法,如下所示:

SearchContext searchContext = SearchContextFactory.getInstance(request);
           

擁有SearchContext對象後,你可以填充各種值,例如要搜尋的關鍵字,分頁類型,開始和結束值等。例如,您可以使用以下值:

searchContext.setKeywords(keywords);
searchContext.setAttribute("paginationType", "more");
searchContext.setStart(0);
searchContext.setEnd(10);
           

關鍵字(keyword)值是最重要的search context屬性,因為它表示您要搜尋的術語(term)或短語(phrase)。 要查找其他SearchContext屬性,請參閱Javadocs。

我們之前已經了解了如何使用IndexerRegistryUtil來擷取索引器的執行個體。 你可以使用以下四種方法中的任何一種:

  • getIndexer(Class<?> clazz)
  • getIndexer(String className)
  • nullSafeGetIndexer(Class<?> clazz)
  • nullSafeGetIndexer(String className)

請記住,當你部署項目時,Liferay會注冊你的索引器。 當你調用上述方法之一時,Liferay将傳回與你提供的類或類名參數對應的索引器類執行個體。 另外,請記住,你的索引器類應該擴充com.liferay.portal.kernel.search.BaseIndexer或至少實作com.liferay.portal.kernel.search.Indexer。Indexer接口定義了一個傳回Hits對象的search(SearchContext searchContext)方法。BaseIndexer抽象類提供了調用SearchEngineUtil.search方法的實作。

雖然可以使用特定的indexer來執行搜尋,但也可以直接使用SearchEngineUtil.search執行搜尋。 SearchEngineUtil處理了搜尋引擎實作的所有複雜性。 進出搜尋引擎實作的所有流量(traffic)都通過此類。 如果你正在debug應用程式的搜尋和索引功能問題,那麼推薦在此類上啟用調試級别日志記錄。

調用indexer的search(SearchContext searchContext)方法或直接調用SearchEngineUtil.search的結果都是Hits對象。Hits對象包含與搜尋查詢比對的Lucene document。 Lucene文檔是Document對象,可以以數組或list形式從Hits對象中檢索。 例如,假設你有一個名為MyEntity的實體的索引器。 進一步假設你有一個名為searchContext的搜尋上下文對象,其中包含你要搜尋的短語(phrase)的keyword。 你可以調用搜尋來擷取這樣的Hits對象:

Indexer indexer = IndexerRegistryUtil.getIndexer("MyEntity");

try {
    Hits hits = indexer.search(searchContext);
}
catch (SearchException se) {
    // handle search exception
}
           

如果要直接使用SearchEngineUtil.search(而不是使用索引器indexer進行搜尋),則需要建立自己的搜尋查詢并確定正确配置search context。 Liferay中的索引器Indexer與特定實體相關聯。 使用索引器進行搜尋時,僅搜尋指定的實體。 您可以使用SearchEngineUtil.search來繞過此功能,該搜尋允許你通過調用search context的searchContext.setEntryClassNames方法來指定要搜尋的實體。 你還應該通過調用search context的searchContext.setCompanyId和searchContext.setGroupIds方法來指定搜尋範圍。

SearchEngineUtil.search是一個重載方法。 此方法最簡單的形式是SearchEngineUtil.search(SearchContext searchContext,Query query)。 要使用此方法,您需要建構自己的查詢。 Liferay提供了基本的Query接口以及擴充它的其他幾個接口,包括:

  • BooleanQuery
  • TermRangeQuery
  • TermQuery

Liferay還提供了幾個query實作和工廠類。 使用Liferay的query工廠類來執行個體化query實作類。 例如,假設你要使用Liferay的搜尋引擎在标題字段中搜尋包含術語(term) liferay的索引文檔。 就可以使用以下代碼:

TermQuery termQuery = TermQueryFactoryUtil.create(searchContext, "title", "liferay");

try {
    Hits hits = SearchEngineUtil.search(searchContext, termQuery);
}
catch (SearchException se) {
    // handle search exception
}
           

如果要搜尋字段值在特定範圍内的索引文檔,可以使用術語範圍查詢(term range query)。 例如,假設你要建構一個query,以查找在某一天(例如2014年12月4日)修改的索引文檔。要構造此類查詢,您可以使用以下代碼:

TermRangeQuery termRangeQuery = TermRangeQueryFactoryUtil.create(searchContext, "modified", "201412040000000", "201412050000000", true, true);

try {
    Hits hits = SearchEngineUtil.search(searchContext, termRangeQuery);
}
catch (SearchException se) {
    // handle search exception
}
           

要以程式設計方式構造更複雜的query,可以使用布爾查詢(boolean query)。 例如,假設你想建構一個query,該query在title 字段中搜尋包含術語liferay 并且description 字段不是lucene 的索引文檔,而且文檔在2014年12月4日被修改過 。要構造此類查詢,您可以使用以下内容 碼:

TermQuery termQuery1 = TermQueryFactoryUtil.create(searchContext, "title", "liferay");
TermQuery termQuery2 = TermQueryFactoryUtil.create(searchContext, "description", "lucene");
TermRangeQuery termRangeQuery = TermRangeQueryFactoryUtil.create(searchContext, "modified", "201412040000000", "201412050000000", true, false);

BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(searchContext);

booleanQuery.add(termQuery1, BooleanClauseOccur.MUST);
booleanQuery.add(termQuery2, BooleanClauseOccur.MUST_NOT);
booleanQuery.add(termRangeQuery, BooleanClauseOccur.MUST);

try {
    Hits hits = SearchEngineUtil.search(searchContext, booleanQuery);
}
catch (SearchException se) {
    // handle search exception
}
           

除了BooleanQuery,TermRangeQuery和TermQuery之外,Liferay還提供了StringQuery實作類。 此query實作允許你使用Lucene的查詢文法構造query。 有關使用StringQuery的示例,請參閱Faceted Search and Customized Search Filtering。 一旦執行了搜尋并獲得了Hits對象,就可以以數組形式檢索相應的文檔,如下所示:

Document[] docs = hits.getDocs();
           

或者以list形式檢索文檔(document):

List<Document> docs = hits.toList();
           

要顯示搜尋結果,你必須周遊數組或文檔清單。每個文檔本質上是索引字段及其值的hash map。有關如何建立便于搜尋和檢視搜尋結果的portlet使用者界面的說明,請參閱Search and Indexing Learning Path。在該Learning Path的示例中,搜尋欄被添加到呈現主portlet視圖的JSP中。送出搜尋查詢時,使用者輸入的短語phrase将作為keyword字元串送出給呈現搜尋結果的JSP。呈現搜尋結果的JSP包含建立和填充search context的代碼,擷取Indexer,使用Indexer進行搜尋,以及檢索與搜尋産生的Hits對應的實體。但是,此代碼不必存在于JSP中。您可以将所有這些邏輯從JSP中提取出來,放入portlet action方法或portlet action方法調用的portlet service方法中。

在本教程中,你學習了如何在portlet項目中為實體建立和注冊索引器。你已經學習了如何更新服務層,以便在對實體執行添加,更新或删除操作時調用索引器。你還了解了如何使用Liferay的搜尋API來配置搜尋上下文(Search Context),執行搜尋以及擷取搜尋結果清單。要了解Liferay搜尋API的更多功能,請參閱有關Faceted Search and Customized Search Filtering分片搜尋和自定義搜尋過濾。

繼續閱讀