驱动力
在许多应用程序中,用户总会提出搜索和查询领域实例的需求。他们或者希望构建一个进入应用程序的入口或者希望填充表单的机制。非常典型的解决方案是用浏览的方式(把领域的继承关系表现出来,这样用户可以定位和选择一个自己需要的)或者一个检索表单的方式(展现一个多个输入域的表单,用户可以检索他们需要的信息)。
现实中,对于可用性的角度来说,这两种方案都不是最佳的。浏览的方式会在有许多分支的时候变得缓慢而笨重。而且,用户通常精确地知道他们要用到那个应用,然而却不情愿要浏览整个系统来找到他要的应用。检索表单的方式同样被检索条件个数的多少限制住了。这就要在设置足够的检索域还是检索表单的复杂性上作出权衡。
从可用性的角度来说,解决这个问题的答案就是提供一个单一的、Google 样式的检索框,用户可以输入任何符合实例字段的内容。他们可以检索和表示符合这些内容的结果。表单中的这个检索框可以自动填充、Google 建议模式的输入框,或者是返回表格式结果的正则表达式搜索。不管怎样,这种解决方案的精髓就是 UI 是简单的,用户可以输入任何他们选择的条件,然后由搜索引擎去做这些复杂的工作。现在唯一的问题时:如何实现这样的搜索机能。
当面对实现传统的多输入域的表单的时候,大部分应用程序都选择了 SQL。典型的情况是,检索的字段都与列名相匹配,并且使用 SQL 的 LIKE 语句。然而,因为复杂的 SQL 要去匹配太多的字段,并且很多情况下由于这些字段的文本长度问题,造成实现的性能经常是非常差的。第二个问题是,对检索结果没有排名并且返回的提示并没有反应出与查询的内容有多大相关性,只是简单地返回结果罢了。第三,对检索结果相关联的关键字没有高亮表示。
很快,大家意识到大部分应用程序需要搜索引擎。所有实体的字段可以像只有一个文件那样被索引,并且是正则文本搜索可以匹配的实体。现在非常流行的搜索引擎之一是 Luence。Lucene 是相当不错的搜索引擎,在很多项目中应用成功。它提供了底层的搜索引擎 API,能够使用 Lucene 数据结构(Document/Field)去索引数据,能供使用查询 API 或搜索引擎在索引上检索。它已经在多种编程语言上实现了全部功能,包括 Java、C#和 C++ 等。
如果我们分析一个典型的 Web 应用程序,一般都有个一个共通的架构和特点。通常,应用与后端的关系数据库一起工作。这个应用使用领域模型表示这个系统中的实体,并使用 ORM 框架把领域模型映射到数据库上。一般情况下,使用一个服务层框架去管理事务、协作,有时也包括业务逻辑和 Web 框架。问题就在于怎么把 Lucene 集成到这样的应用程序中去。
当你试图去集成 Lucene 的时候,刚刚把第一个简单的程序跑起来的时候,马上就会遇到一连串的挑战。第一个问题就是索引应用数据。在之前很长一段时间,相当多的样板式代码热衷于把领域模型映射到 Lucene 数据模型上去。Lucene 文档,是 Lucene 主要的数据结构,它是一个扁平的类似 Map 的,只包含字符串的数据结构——所以许多无意义的代码热衷于“植入”和“植出”领域模型。另外一个问题是缺少对 Lucene 的事务控制,把领域模型数据存储到数据库和搜索引擎是有问题的。而且还有几个其他的很有名的实践和模式要在 Lucene 中实现,比如缓存、隐式的搜索、为支持 Google 样式的搜索而创建聚集的属性和为合适的语义保持可识别的 Document 对象,等等。
Compass 简介
Compass 的设计目标是简化企业在集成搜索功能时的花费。Compass 是在 Lucene 之上,使用了设计很好的搜索引擎的抽象。Compass 扩展了核心 Lucene,增加了事务控制功能和快速更新,也包括在数据库存储索引的功能。当然,它没有去隐藏 Lucene 的特性——所有 Lucene 的功能都能通过 Compass 实现。
Compass 核心 API
Compass 提供给了我们简单的并且熟悉的 API。说 Compass 提供了让人熟悉的 API 是因为它模仿了当前流行的 ORM 框架的 API 来降低学习曲线。Compass 以下面一些主要的接口作为主要内容:
- CompassConfiguration:用来在一些设置参数、配置文件和映射定义上配置 Compass。通常用来创建 Compass 接口。
- Compass:为单线程使用,创建线程安全的实例来打开 Compass Seesion。同样还提供了一些搜索引擎索引级别的操作。
- CompassSesssion:用来执行像保存、删除、查找、装载这样的搜索操作。很轻量但是并不是线程安全的。
- CompassTransaction:管理 Compass 事务的接口。使用它并不需要事务管理环境(像 Spring、JTA)。
下面是使用这些 API 的一个简单的例子:
// 在程序中配置和创建 Compass<br></br> CompassConfiguration conf =<br></br> new CompassConfiguration().setConnection("/tmp/index").addClass(Author.class);<br></br> Compass compass = conf.buildCompass();<p> // 一个请求操作 </p><br></br> CompassSession session = compass.openSession();<br></br> CompassTransaction tx = null;<br></br> try {<br></br> tx = session.beginTransaction();<br></br> ...<br></br> session.save(author);<br></br> CompassHits hits = session.find("jack london");<br></br> Author a = (Author) hits.data(0);<br></br> Resource r = hits.resource(0);<br></br> ...<br></br> tx.commit();<br></br> } catch (CompassException ce) {<br></br> if (tx != null) tx.rollback();<br></br> } finally {<br></br> session.close();<br></br> }
为了简化事务管理代码,Compass 提供了好几种选择,首先是使用 CompassTemplate,它使用流行的设计模式来抽象事务管理。第二个选择是在和事务管理环境下,这样,Compass 与 JTA 与 Spring 这样的事务管理器集成并在一个已经存在的事务中执行。这个情况下,当一个 Session 执行的时候,CompassSession 可被用做一个自动加入事务处理的代理。这个代理的创建可以是编程式的,也可使用 Spring IOC(Spring 2 中支持 @CompassContext)。
Compass 支持原子性的事务运算,与不同的事务管理策略集成,包括本地事务管理、JTA 同步、XA for JTA 的集成,Spring 同步的集成。
Compass 的配置基于“键—值”的一一对应的设置。Compass 可以使用编程式的配置,基于 XML DTD 的配置(定义映射和设置),基于 XML Schema 的配置。基于 XML Schema 的配置得到了 Spring2 新的基于 Schema 配置文件的支持。
搜索引擎映射
Compass 的主要功能之一就是从应用程序模型到搜索引擎的声明式映射。Compass 搜索引擎的领域模型由资源(Lucene Document)和属性(一个 Lucene Field)组成。这是用来索引可搜索内容的抽象数据对象。
RSEM
第一个映射是 RSEM(Resource/SearchEngine Mapping)。这是一个低级别从 Compass 资源和属性到搜索引擎抽象到搜索引擎的映射。下面是个对作者资源的 RSEM 的示例:
<resource alias="author"><br></br> <resource-id name="id"/><br></br> <resource-property name="firstName"/><br></br> <resource-property name="lastName" store="yes" index="tokenized"/><br></br> <resource-property name="birthdate" converter="mydate"/><br></br> </resource><br></br>
上面的例子中,我们定义了一个映射了作者别名的资源。这个资源的映射包括标识资源的 ID 和几个附加的属性。定义属性是可选的,尽管他们允许声明式的控制不同属性的特征,包括和一个转换器关联。下面的示例代码填充了一个资源并索引它。
Resource r = session.createResource("author");<br></br> r.addProperty("id", "1")<br></br> .addProperty("firstName", "jack")<br></br> .addProperty("lastName", "london")<br></br> .addProperty("birthdate", new Date());<br></br> session.save(r);<br></br>
上面的代码显示了一些 Compass 的特性。第一,由于一个资源是可识别的,Compass 在这个资源已经存在的情况下更新它。第二,可以声明式的分配一个转换器给这个资源,可以使用 Compass 内置的许多转换器。下面是上面示例代码的 Compass 配置(包括对 mydate 转换器的配置):
<compass-core-config xmlns="http://www.opensymphony.com/compass/schema/core-config" <br></br> xsi:schemaLocation="http://www.opensymphony.com/compass/schema/core-config <br></br> http://www.opensymphony.com/compass/schema/compass-core-config.xsd"> <br></br> <compass name="default"> <br></br> <connection> <br></br> <file path="index" /> <br></br> </connection> <br></br> <converters> <br></br> <converter name="mydate" type="org.compass.core.converter.basic.DateConverter"> <br></br> <setting name="format" value="yyyy-MM-dd" /> <br></br> </converter> <br></br> </converters> <br></br> <mappings><br></br> <resource location="sample/author.cpm.xml" /><br></br> </mappings><br></br> </compass> <br></br> </compass-core-config><br></br>
### OSEM
OSEM(Object/Search Engine Mapping) 是第二个支持的映射方案。它允许把应用对象的领域模型映射到搜索引擎。下面是 Author 类,使用注释对它进行了 OSEM 定义:
@Searchable<br></br> public class Author {<p> @SearchableId</p><br></br> private Long id;<p> @SearchableComponent</p><br></br> private String Name;<p> @SearchableReference</p><br></br> private List<book> books;<p> @SearchableProperty(format = "yyyy-MM-dd")</p><br></br> private Date birthdate;<br></br> }<p> // ...</p><p> @Searchable</p><br></br> public class Name {<p> @SearchableProperty</p><br></br> private String firstName;<p> @SearchableProperty</p><br></br> private String lastName;<br></br> } </book>
OSEM 支持“植入”和“植出”一个对象的分层结构进入一个资源。当存储一个 Author 对象,Compass 就会“植入”进一个资源,Name 类也会“植入”进相同的资源来表示这个作者(由于组件的映射),也包括一个这个作者书籍列表里的每一本书(存储在其他的资源里)的引用。这个最后得到的资源会存储或者索引在搜索引擎中。
Compass 提供了非常灵活的机制来把领域模型映射到搜索引擎中。上面的例子只是一个很简单的例子。OSEM 允许制定不同的转换器,一个类属性对应多个元数据(从资源到属性的映射)、分析器和所有参与的字段,等等。
下面是 author 类怎样使用的例子:
// ...<br></br> Author author = new Author(1, new Name("jack", "london"), new Date());<br></br> session.save(author);<br></br> // ...<br></br> author = (Author) session.load(Author.class, 1);
### XSEM
最后,Compass 支持的搜索引擎映射是 XSEM(Xml/Search Engine Mapping)。这种映射允许基于 XML 映射的定义(用 XPath 实现),把 XML 数据结构直接映射到搜索引擎。XSEM 的处理同样的通过对资源“植入”和“植出”的处理。Compass 提供了一个 XML 包装对象叫做 XmlObject,它定义了不同的实现 (dom4j, W3C Document),这些实现允许 XPath 表达式来求值。如果我们给出下面的 XML 数据结构:
<xml-fragment><br></br> <author id="1"><br></br> <firstName>Jack</firstName><br></br> <lastName>London</lastName><br></br> </author><br></br> </xml-fragment><br></br>
下面是个 XSEM 的实现:
<compass-core-mapping><br></br> <xml-object alias="author" xpath="/xml-fragment/author"><br></br> <xml-id name="id" xpath="@id" /><br></br> <xml-property xpath="firstName" /><br></br> <xml-property xpath="lastName" /><br></br> <xml-content name="content" /><br></br> </xml-object><br></br> </compass-core-mapping><br></br>
从 XML 数据结构到搜索引擎的映射是使用 XPath 表达式来完成。XML 内容映射可以在搜索引擎中存储为 XML 结构,这样就可以加载和搜索数据。Compass 支持多种 XML DOM 框架(为 XML 内容作映射),包括 JSE5, dom4j(SAX 和 XPP),当然定制的实现也很好做。下面是个不错的例子:
Reader reader = // construct an xml reader over raw xml content<br></br> AliasedXmlObject xmlObj = RawAliasedXmlObject("author", reader);<br></br> session.save(xmlObj);<br></br> // ...<br></br> Resource resource = session.loadResource("author", 1);<br></br> // since we have xml-content, we can do the following as well<br></br> XmlObject xmlObj = session.load("author", 1);
## Compass Gps
Compass Gps 是 Compass 的一个组件,用来把不同的数据源与 Compass 集成。大部分常用的数据源是 Compass 与 ORM 工具的集成。Compass 支持 JPA、Hibernate、OJB、JDO 和 iBatis。
拿 Hibernate 作为例子,Compass 给出了两个主要的操作:索引与镜像。拥有这两个映射的对象可以通过使用 Hibernate API 注册时间监听,进行自动的镜像操作到搜索引擎。下面的例子给出了怎样使用 Compass Gps 集成 Hibernate:
SessionFactory sessionFactory = // Hibernate Session Factory<br></br> Compass compass = // set up a Compass instance<br></br> CompassGps gps = new SingleCompassGps(compass);<br></br> CompassGpsDevice device = new Hibernate3GpsDevice("hibernate", sessionFactory);<br></br> gps.addDevice(device);<br></br> // start the gps, mirroring any changes made through Hibernate API<br></br> // to be mirrored to the search engine<br></br> gps.start();<p> // ....</p><p> // this will cause the database to be indexed</p><br></br> gps.index();<br></br> // this will cause Hibernate to store the author in the database<br></br> // and also index the author object through Compass<br></br> hibernateSess.save(new Author(1, new Name("jack", "london"), new Date()));<br></br>
## 总结
这篇文章对 Compass 的主要功能的做了介绍,但只是覆盖了怎样使用 Compass 的基本功能(尤其,Compass 还有个与 Spring 集成的扩展组件,这个并没介绍)。在使用搜索引擎的时候,Compass 同样也有很多现在流行功能和有一些细微的差别功能,还有配置扩展功能。Compass 的主要目标,像刚才提到的,是简化集成搜索到任何类型的应用程序中,这篇文章只是介绍了怎么使用的基本信息。
关于作者
Shay 是 Compass 开源项目的建立者,Compass 是唯一集成搜索功能到各种应用模型中的解决方案。他先是专注于实时的 C/C++ 系统,后来转到 Java 开发 (不再回头)。在 Java 世界中, Shay 最近在实现分布式规则引擎服务器的工作。这是一个典型的 Java 为基础的 Web 项目,面向金融行业、以消息为基础的项目。现在 Shay 是 GigaSpaces 的系统架构师。
查看英文原文: Compass: Integrate Search into your apps - - - - - -
译者简介: 周刚,毕业于北京邮电大学应用数学专业,现就职于 BULL。目前主要关注以 Spring Framework 和 JPA 为主干的 JavaEE 和 JavaFX 技术,重视对 JavaSE 基础的知识的传播,尽已所能参与技术社区的内容建设工作,以期共同提高技术水平。参与 InfoQ 中文站内容建设,请邮件至 china-editorial@infoq.com ,加入 InfoQ 中文站用户讨论组,请点击 ICUG,InfoQ China User Group 。
评论