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

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分片搜索和自定义搜索过滤。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值