Hibernate Search 和 Lucene 的快速介绍

jopen 8年前

在上篇博客“为你的Hibernate应用程序添加搜索功能的最简单的方法”中我谈到了一些在你应用中整合Hibernate Search的场景,还讲到了一些Hibernate Search以及它与Hibernate Core, Lucene 和Solr之间的联系。这篇博客中我们将快速浏览一个示例(实际上它是一个JUnit测试用例),这个示例将会用Hibernate Core和Java Persistence API(JPA)注解来持久化一个简单实体到一个关系数据库,并且利用Hibernate Search通过Lucene索引的创建/更新来对数据库中更新了的hibernate管理实体进行搜索。

有些碎碎念我嚼的在深入前得先明确一下:

1. 该示例是基于Hibernate Search 4.3.0 , 源码已经被打成Eclipse项目附在文章末位处。你可能需要m2eclipse插件来导入这个项目,或者你也可以直接编译然后运行测试用例。

2.当你在执行新增/更新/删除操作时,Hibernate Search 将自动的生成和维护Lucene索引,不需要写代码去专门维护。

3.基本上靠JPA和Hinernate Search的注解就可以驱动Hibernate框架干大部分有关创建表,持久化测试数据和让Lucene构建和弹出可查询的索引和数据的工作。

4 因为这是个Junit测试,我们会利用H2的内存数据库的优势和Lucene索引能力的优势。如果你想转到硬盘数据库环境中去保存数据和索引,只需要在Hinernate的xml配置文件做少许的改动即可。

5.这片博客的目的是演示如何用Hinernate Search来让Hibernate管理的实体可查询和如何用Lucene索引来查询。这个演示只是告诉你在基于Hibernate的应用中添加查询功能是个很简单的事。其实Lucene还有很多东西需要了解,例如Text Tonkenizers 和 analyzers 的工作原理到底是啥。并且Lucene和Hibernate Search组合还能干好多牛逼活,我的这片博客仅仅说了个皮毛而已。如果这片文章引起你的注意了,我希望你关注LuceneHinernate Search 项目和其项目网站。

我将会用一个很简单的例子来演示你如何上手Hibernate Search。 在这个例子里 ,我们有一个汽车实体将会被持久化。我们的单元测试会持久化这个实体到我们的H2数据库的内存存储区间里。我会用JPA注解指引Hibernate如何去持久化这个实体。Hibernate会自动在数据库中创建表结构。我还会用Hibernate Search 注解来通知框架给我们的实体在哪一个字段上建立索引并且这个被索引的字段的内容在查询时是否需要返回该字段所存储的数据。最后,我们将把Hibernate 和 Hibernate Search 通过配置文件结合起来并且创建一个Junit 测试用例,该用例会在数据库中生成一些汽车实体并且触发Hibernate Search把要索引的字段发送给Lucene。每一个单元测试的实现方法都通过调用HibernateSearch和Lucene的API来实现我们殊途同归的查询。

下面就是我们希望Hibernate为我们保存的Car实体,我们使用了JPA注解比如@Entity,@Id和@GeneratedValue,它们将分别告诉Hibernate JPA这个类是需要被持久化的,id字段被用作主键,而且我们希望数据库为我们自动生成id值。我们也使用了几个Hibernate Search的注解:

  • @Indexed: 标明这个实体需要被Lucene创建索引,从而使之可以被检索
  • @Analyzer: 告诉Hibernate Search来标记它的域以及更新Lucene索引的时候使用哪个Lucene分析器。注意:你以后检索的时候,使用一个与Lucene为你将要检索的文件创建索引的时候使用的分析器相同的分析器是非常重要的。然后使用一个不同的分析器可能也会返回我们想要的结果,但是这得不到保证,所以,总是先研究你选择的用来创建索引和检索的分析器,然后再做出明智的选择。
  • @DocumentId:标明Car的id字段应该被用作Lucene索引中文档的ID,这几乎总是和数据库中实体的主键是同一个字段。
  • @Field: 告诉Hibernate Search为该字段创建爱你索引,并且提供一些其他信息,比如该字段在索引中需要被如何处置。
@Entity  @Indexed  @Analyzer(impl = org.apache.lucene.analysis.standard.StandardAnalyzer.class)  public class Car {     @Id   @GeneratedValue   @DocumentId   private Long id;     @Column   @Field(store = Store.YES)   private String make;     @Column   @Field(store = Store.YES)   private String model;     @Column   @Field(store = Store.YES)   private short year;     @Column   @Field(store = Store.NO)   private String description;     public Car() {   }     public Car(String make, String model, short year, String description) {    super();    this.make = make;    this.model = model;    this.year = year;    this.description = description;   }     public String getMake() {    return make;   }      // more getters/setters
前面所讲的就是Hibernate持久化Car对象和Hibernate Search 让Cars变得可搜索所需要的。现在,我们来看一下如何搜索Cars,首先,我们需要加载Hibernate配置文件,然后创建一个数据库Session:
@Before  public void setUp() throws Exception {   Configuration configuration = new Configuration();   configuration.configure("hibernate-test-cfg.xml");   ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties())    .buildServiceRegistry();   hibernateSessionFactory = configuration.buildSessionFactory(serviceRegistry);   hibernateSession = hibernateSessionFactory.openSession();   populateDBWithTestData();  }

我们现在可以持久化一些Car对象来测试一下:

private void populateDBWithTestData() {          Car[] testCars = { new Car("Shelby American", "GT 350", (short) 1967, "This is Tim's car!"),     new Car("Chevrolet", "Bel Air", (short) 1957, "This is a true classic") };     Transaction tx = hibernateSession.beginTransaction();     hibernateSession.save(testCars[0]);   hibernateSession.save(testCars[1]);     tx.commit();  }

现在我们的两个测试用的Car对象已经保存到H2数据库中了,而且Lucene也已经对它们创建了索引!我们可以通过Lucene来搜索Cars:

@Test  public void testUsingLuceneBooleanQueryReturningFullEntity() throws Exception {   FullTextSession fullTextSession = Search.getFullTextSession(hibernateSession);     BooleanQuery bq = new BooleanQuery();   TermQuery gt350TermQuery = new TermQuery(new Term("model", "GT 350"));   TermQuery belAirTermQuery = new TermQuery(new Term("model", "Bel Air"));   bq.add(gt350TermQuery, BooleanClause.Occur.SHOULD);   bq.add(belAirTermQuery, BooleanClause.Occur.SHOULD);   Query q = new QueryParser(Version.LUCENE_36, "cs-method", new StandardAnalyzer(Version.LUCENE_36)).parse(bq    .toString());     org.hibernate.Query hibernateQuery = fullTextSession.createFullTextQuery(q, Car.class);   List searchResults = hibernateQuery.list();     boolean foundShelby = false;   boolean foundBelAir = false;   for (Car car : searchResults) {    if (car.getModel().equals("GT 350")) {     foundShelby = true;    } else if (car.getModel().equals("Bel Air")) {     foundBelAir = true;    }   }   Assert.assertEquals(2, searchResults.size());   Assert.assertTrue(foundShelby && foundBelAir);  }
关于上面的搜索代码,这里有几个关键点:
  • 第3行,我们获取了一个Hibernate Search FullTextSession。需要注意的是这个session装饰了一个正常的Hibernate数据库Session对象,这让Hibernate Search能够清楚创建过索引的对象的插入、更新、删除操作,从而保证Lucene的索引是最新的。
  • 从第5行到第13行,我们创建了一个查询,来检索我们测试时保存的两个Car对象,我们选择使用Lucene的BooleanQuery来查询,尽管当你现在了全部的源码之后你会发现,有很多方法都可以创建类似的查询。这个查询将执行这样的操作:“查找所有model属性值为‘GT350’或者‘Bel Air’的Car对象”。我们创建了查询对象,然后让Lucene QueryParser对查询对象进行了分析,然后用FullTextSession将它翻译成标准的Hibernate查询。
  • 剩下的代码用来检查我们创建的查询是否的确返回了我们期望在Lucene索引中查找到的值,然后使用断言进行验证。

如果你想了解创建跟上面类似的查询的其他方式或者想加载上面的代码然后亲自尝试一下Hibernate Search,你可以下载本篇文章的Hibernate Search实例代码

我们的测试用例使用的Hibernate配置文件非常简单。它配置了我们要用的H2数据库,告诉将Hibernate指给了我们的Car实体类,然后告诉Hibernate Search存放Lucene索引的位置。我只想对Hibernate配置文件(在可供下载的工程里,它的名字是"hibernate-test-cfg.xml")的一个部分进行说明。

<!-- 将索引保存到内存中,所以测试后不需要进行索引清理 -->  org.hibernate.search.store.impl.RAMDirectoryProvider    <!-- 在生产应用中需要设置下面的内容,将索引保存到硬盘上. -->  <!--  <property name="hibernate.search.default.directory_provider">   org.hibernate.search.store.impl.FSDirectoryProvider  </property>  <property name="hibernate.search.default.indexBase">c:/temp/lucene/indexes</property>  -->    <!-- 定义Hibernate实体映射. 标准的Hibernate配置,无需指定Hibernate Search. -->  <mapping class="net.timontech.hibernate.search.Car"/>

正如我上面提到的,我们同时保存了数据库和Lucene索引在内存中。我们这样做只是为了测试,因为这样我们的单元测试就没有可以清理的数据。你是不可能在生产环境中这样做的。上面的配置文件中,Hibernate Search提供了一个用于将索引保存到内存中的RAMDirectoryProvider和一个允许你指定索引保存位置的FSDirectoryProvider。把索引存储位置从内存调整到电脑硬盘就如同修改一个Hibernate Search属性一样简单。

上面配置文件中的的“mapping”节点告诉Hibernate Core检查Car类中的注解,这些注解将指示hibernate如何将Car实体持久化到数据库中。

很明显,关于Hibernate Search和Lucene还有很多东西需要了解,Lucene本身就是一个既强大又灵活的库。然而,如果你还没有接触过Hibernate Search或者Lucene,我希望这篇文章能够让你体会一下这些技术的“味道”并且能为你的入门提供足够的信息。如果对你来说Hibernate Search是完全陌生的,我强烈建议你下载附件中的工程,将它导入到Eclipse中,然后看一下Hibernate Search和Lucene的API,然后试一下它们的功能。试过之后,如果你还不了解Lucene,我建议你学习一下Lucene的文档。