首頁 > 軟體

Hibernate Search 和 Lucene 的快速介紹

2020-06-16 17:49:37

在上篇部落格“為你的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範例程式碼

------------------------------------------分割線------------------------------------------

免費下載地址在 http://linux.linuxidc.com/

使用者名稱與密碼都是www.linuxidc.com

具體下載目錄在 /2015年資料/11月/26日/Hibernate Search 和 Lucene 的快速介紹/

下載方法見 http://www.linuxidc.com/Linux/2013-07/87684.htm

------------------------------------------分割線------------------------------------------

我們的測試用例使用的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的文件。

基於Lucene多索引進行索引和搜尋 http://www.linuxidc.com/Linux/2012-05/59757.htm

Lucene 實戰(第2版) 中文版 配套原始碼 http://www.linuxidc.com/Linux/2013-10/91055.htm

Lucene 實戰(第2版) PDF高清中文版 http://www.linuxidc.com/Linux/2013-10/91052.htm

使用Lucene-Spatial實現整合地理位置的全文檢索 http://www.linuxidc.com/Linux/2012-02/53117.htm

Lucene + Hadoop 分散式搜尋執行框架 Nut 1.0a9 http://www.linuxidc.com/Linux/2012-02/53113.htm

Lucene + Hadoop 分散式搜尋執行框架 Nut 1.0a8 http://www.linuxidc.com/Linux/2012-02/53111.htm

Lucene + Hadoop 分散式搜尋執行框架 Nut 1.0a7 http://www.linuxidc.com/Linux/2012-02/53110.htm

Project 2-1: 設定Lucene, 建立WEB查詢系統[Ubuntu 10.10] http://www.linuxidc.com/Linux/2010-11/30103.htm

在 Hibernate Search 5.5 中對搜尋結果進行排序  http://www.linuxidc.com/Linux/2015-09/123578.htm


IT145.com E-mail:sddin#qq.com