Hibernate Search (Part 1)

01 Jul 2013

今天參加了一個面試, 聊到了差不多去年這個時候做的全文搜索功能, 感覺自己總結得實在是太少了, 明明知道的一些東西, 問起來卻總是一下答不上來...

業務場景其實不算複雜, 就是要做到類似Google 搜索那樣, 通過一個輸入框來搜索系統中多個表/ 多個字段中包含了所輸入內容的記錄集, 也就是在幾個表的字段中, 如果包含了輸入的內容, 那麼對於的記錄就是符合的.

因爲主要的業務對象是數據庫, 而不是文檔, 所以技術選型的結果就是使用Hibernate Search 來實現這個全文搜索功能, 主要是基於下面幾點考慮:

  1. 主要的業務對象並不是文檔, 而是數據庫

  2. Hibernate Search 跟Hibernate 結合得更緊密, 可以直接通過Hibernate 實體類來操作Lucene 的索引, 只需要實體類中添加Annotation, 然後在Hibernate 配置文件中添加listener, 即可以在數據新增/ 修改/ 刪除時自動更新索引.

  3. 數據查詢同樣可以借助Hibernate, 減少了從索引中獲取數據後再轉折去查詢數據庫的操作.

由於內容較多, 這一節先只介紹索引的處理.

  • 首先當然就是添加Hibernate Search 的依賴了

  • 在要創建Lucene 索引的實體類中添加Annotation, 如下:

 1 @Indexed
 2     public class Device {
 3         @DocumentId
 4         private long id;
 5         
 6         @Field(name = "deviceNname", index = Index.TOKENIZED, store = Store.YES)
 7         private String name;
 8         
 9         private String type;
10         
11         //... getter setter
12     }
 1 @Indexed
 2     public class Alarm {
 3         @DocumentId
 4         private long id;
 5         
 6         @Field(name = "alarmName", index = Index.TOKENIZED, store = Store.YES)
 7         private String name;
 8         
 9         @IndexedEmbedded
10         private Device device;
11         
12         //... getter setter
13     }
這裏, 主要是@Field 的Annotation, 其中的name 屬性即是生成的Lucene 字段名稱
  • 接下來自然是Hibernate 的配置文件了, 和正常的配置文件差不多, 除了要添加Lucene 的索引保存路徑, 如果要在新增/ 修改/ 刪除實體時自動更新索引, 還需要添加listener.
1 <!-- 配置索引的保存方式, 這裡配置為使用文件進行保存 -->
2     <property name="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</property>
3     <!-- 配置索引的保存路徑 -->
4     <property name="hibernate.search.default.indexBase">./var/lucene/indexes</property>
5     <!-- 是否使用listener 監聽實體的改變 -->
6     <property name="hibernate.search.autoregister_listeners">true</property>
 1 <!-- 這裡沒有直接使用hibernate 的配置方式了, 如果使用spring 進行配置, 則更加簡單 -->
 2     <event type="post-update">
 3         <listener class="org.hibernate.search.event.FullTextIndexEventListener" />
 4     </event>
 5     <event type="post-insert">
 6         <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
 7     </event>
 8     <event type="post-delete">
 9         <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
10     </event>
11     
12     <event type="post-collection-recreate">
13         <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
14     </event>
15     <event type="post-collection-remove">
16         <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
17     </event>
18     <event type="post-collection-update">
19         <listener class="org.hibernate.search.event.FullTextIndexEventListener"/>
20     </event>
21     <event type="flush">
22         <listener class="org.hibernate.event.def.DefaultFlushEventListener"/>
23     </event>
  • 如果像上面那樣配置了listener, 那麼這樣就可以了, 在實體內容發生變化時, 會自動更新對於的索引. 如果不自動監聽實體的改變, 那麼就需要自己在實體改變的時候去更新索引.
1 Session session = null; // ... get session;
2     FullTextSession fullTextSession = Search.getFullTextSession(session);
3     Transaction tx = fullTextSession.beginTransaction();
4     // 新建/ 更新索引
5     fullTextSession.index(po);
6     // 刪除索引
7     // fullTextSession.purge(po.getClass(), po.getId());
8     tx.commit();

至此, 創建索引的操作就算是完成了, 這比直接使用Lucene 要簡單很多.