文章目錄
- 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的搜尋庫。 要實作實體的搜尋和索引功能,你需要執行以下三個步驟:
- 在portlet項目中建立一個*Indexer類,并在項目的liferay-portlet.xml檔案中注冊該類。
- 更新實體的服務層(service layer),以便在增删改實體時同步更新索引。
- 提供執行搜尋的機制。 例如,你可以在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分片搜尋和自定義搜尋過濾。