1. 程式人生 > >[Hibernate Search] (3) 基礎查詢

[Hibernate Search] (3) 基礎查詢

osi 工作 ring 效率 問號 min 關鍵字 name -h

基礎查詢

眼下我們僅僅用到了基於keyword的查詢,實際上Hibenrate Search DSL還提供了其他的查詢方式,以下我們就來一探到底。

映射API和查詢API

對於映射API。我們能夠通過使用Hibernate提供的註解來完畢映射工作。同一時候我們也能夠使用JPA提供的註解來完畢。類似的,對於查詢API,我們也能夠從Hibernate和JPA提供的查詢API中進行選擇。

每種方式都有它的長處和缺點,比方當我們使用Hibernate提供的查詢API時,意味著能夠使用很多其它的特性,畢竟Hibernate Search就是建立在Hibernate之上的。而當我們選擇JPA的查詢API時,意味著應用能夠更方便的切換ORM的實現。比方我們想將Hibernate替換成EclipseLink。

Hibernate Search DSL

所謂的Hibernate Search DSL,實際上就是用於編寫查詢代碼的一些列API:

import org.hibernate.search.query.dsl.QueryBuilder;

// ...

String searchString = request.getParameter("searchString");
QueryBuilder queryBuilder = fullTextSession.getSearchFactory()
    .buildQueryBuilder().forEntity( App.class ).
get(); org.apache.lucene.search.Query luceneQuery = queryBuilder .keyword() .onFields("name", "description") .matching(searchString) .createQuery();

它採用鏈式編程的方式將查詢中關鍵的部分封裝成一個個方法進行連續調用。當下,非常多API都被設計成這樣。比方jQuery的API。以及Java 8中最新的Stream類型的API等。同一時候,一些設計模式如建造者模式也大量地使用了這樣的技術。

關鍵字查詢(Keyword Query)

基於keyword的查詢。是最為主要的一種查詢方式。眼下見到的樣例都是基於keyword查詢的。 為了運行這樣的查詢,第一步是得到一個QueryBuilder對象,而且說明須要查詢的目標實體:

QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder()
    .forEntity(App.class).get();

下圖反映了在創建keyword查詢時可能的流程:

技術分享

反映到代碼中是這種:

org.apache.lucene.search.Query luceneQuery = queryBuilder
    .keyword()
    .onFields("name", "description", "supportedDevices.name", "customerReviews.comments")
    .matching(searchString)
    .createQuery();

onFields方法能夠看做是多個onField方法的組合,為了方便一次性地聲明全部查詢域。 假設onFields中接受的某個域在相應實體的索引中不存在相關信息,那麽查詢會報錯。所以,須要確保傳入到onFields方法中的域確實是存在於實體的索引中的。

對於matching方法,通常而言它須要接受的是一個字符串對象,表示查詢的keyword。可是實際上借助FieldBridge,傳入到該方法的參數能夠是隨意類型。在“高級映射”一文中會對FieldBridge進行介紹。

對於傳入的keyword字符串,它或許包括了多個keyword(使用空白字符分隔,就像我們使用搜索引擎時)。Hibernate Search會默認地將它們切割成一個個的keyword,然後逐個進行搜索。

終於,createQuery方法會結束DSL的定義並返回一個Lucene查詢對象。最後,我們能夠通過FullTextSession(Hibernate)或者FullTextEntityManager(JPA)來得到終於的Hibernate Search查詢對象(FullTextQuery):

FullTextQuery hibernateQuery =
    fullTextSession.createFullTextQuery(luceneQuery, App.class);

模糊查詢(Fuzzy Query)

當我們使用搜索引擎時,它都可以非常“聰明”地對一些輸入錯誤進行更正。而在Hibernate Search中,我們也可以通過模糊查詢來讓查詢更加智能。

當使用了模糊查詢後,當keyword和目標字串之間的匹配程度低於設置的某個閾值時,Hibernate Search也會覺得匹配成功而返回結果。

這個閾值的範圍在0和1之間:0代表不論什麽字串都算匹配,而1則代表僅僅有全然符合才算匹配。

所以當這個閾值取了0和1之間的某個值時,就代表查詢可以支持某種程度的模糊。

當使用Hibernate Search DSL來定義模糊查詢時。可能的流程例如以下:

技術分享

它一開始使用的也是keyword方法來定義一個基於keyword的查詢,畢竟模糊查詢也僅僅是keyword查詢的一種。 它在最後也會使用onField/onFields來指定查詢的目標字段。

僅僅只是在keyword和onField/onFields方法中間會定義模糊查詢的相關參數。

fuzzy方法會使用0.5作為模糊程度的默認值,越接近0就越模糊,越接近1就越精確。因此。這個值是一個折中的值,在多種環境中都可以通用。

假設不想使用該默認值,還能夠通過調用withThreshold方法來指定一個閾值:

luceneQuery = queryBuilder
    .keyword()
    .fuzzy()
    .withThreshold(0.7f)
    .onFields("name", "description", "supportedDevices.name", "customerReviews.comments")
    .matching(searchString)
    .createQuery();

除了withThreshold方法外。還能夠使用withPrefixLength方法來指定每一個詞語中,前多少個字符須要被排除在模糊計算中。

通配符查詢(Wildcard Query)

在通配符查詢中,問號(?

)會被當做一個隨意字符。而星號(*)則會被當做零個或者多個字符。

在Hibernate Search DSL中使用通配符搜索的流程例如以下:

技術分享

須要使用wildcard方法來指定它是一個支持通配符的查詢。

精確短語查詢(Exact Phrase Query)

前面提到過。Hibernate Search會在運行查詢前將keyword使用空白字符進行切割,然後對得到的詞語逐個查詢。

然而,有時候我們須要查詢的就是一個完整的短語,不須要Hibernate Search多此一舉。在搜索引擎中。我們通過使用雙引號來表示這樣的情況。

在Hibernate Search DSL中,能夠通過短語查詢來完畢,一下是流程圖:

技術分享

sentence方法接受的參數必須是一個String類型,這一點和matching有所不同。

withSlop方法接受一個整型變量作為參數,它提供了一種原始的模糊查詢方式:短語中額外能夠出現的詞語數量。比方我們要查詢的是“Hello World”,那麽在使用withSlop(1)後,“Hello Big World”也會被匹配。

那麽在詳細的代碼中,我們能夠首先進行推斷,假設搜索字符串被引號包括了。那麽就使用短語查詢:

if(isQuoted(searchString)) {
    luceneQuery = queryBuilder
        .phrase()
        .onField("name")
        .andField("description")
        .andField("supportedDevices.name")
        .andField("customerReviews.comments")
        .sentence(searchStringWithQuotesRemoved)
        .createQuery();
}

範圍查詢(Range Query)

範圍查詢的流程:

技術分享

顧名思義,範圍查詢通過給定上限值和下限值來對某些域進行的查詢。

因此。日期類型和數值類型一般會作為此類查詢的目標域。

above。below方法用來單獨指定下限值和上限值。而from和to方法必須成對使用。 它們能夠結合excludeLimit來將區間從閉區間轉換為開區間:

比方from(5).to(10).excludeLimit()所代表的區間就是:5 <= x < 10。

以下是一個查詢擁有4星及以上評價的App實體:

luceneQuery = queryBuilder
    .range()
    .onField("customerReviews.stars")
    .above(3).excludeLimit()
    .createQuery();

布爾(組合)查詢(Boolean(Combination) Query)

假設一個查詢滿足不了你的需求,那麽你能夠使用布爾查詢將若幹個查詢結合起來。

以下是它的流程:

技術分享

使用bool方法來表明這個查詢是一個組合查詢,會組合多個子查詢。

它至少須要包括一個must子查詢或者一個should查詢。

must和should分別表示的是邏輯與(Logical-AND)和邏輯或(Logical-OR)的語義。

一般。不要同一時候使用must和should,由於這會讓should中的查詢毫無意義。僅僅有在須要依據相關度對結果的排序進行調整時,才會將must和should聯合使用。

比方。下述代碼用來查詢支持設備xPhone而且擁有5星評價的App實體:

luceneQuery = queryBuilder
    .bool()
    .must(
        queryBuilder
            .keyword()
            .onField("supportedDevices.name")
            .matching("xphone")
            .createQuery()
    )
    .must(
        queryBuilder
            .range()
            .onField("customerReviews.stars")
            .above(5)
            .createQuery()
    )
    .createQuery();

排序(Sorting)

默認情況下,查詢結果應該依照其和查詢條件間的相關度進行排序。關於相關度排序,會在興許的文章中介紹。

可是我們也能夠不再使用相關度作為排序的根據,轉而我們能夠使用日期,數值類型甚至字符串的順序作為排序根據。

比方,對App的搜索結果。我們能夠使用其名字在字母表中的順序進行排序。

為了支持對於某個域的排序。我們須要向索引中加入一些必要的信息。在對字符串類型的域進行索引時,默認的分析器會將該域的值進行分詞,所以對於某個值“Hello World”,在索引中會有兩個入口對“Hello”和“World”進行單獨保存。這樣做可以讓查詢更具效率,可是當我們須要對該域進行排序時,分詞器是不須要的。

因此,[email protected]

@Column
@Fields({
    @Field,
    @Field(name="sorting_name", analyze=Analyze.NO)
})
private String name;

一個用來建立標準的索引,一個用來建立用於排序的索引,當中指定了analyze=Analyze.NO,默認情況下分詞器是被使用的。

這個域就能夠被用來創建Lucene的SortField對象,並集合FullTextQuery使用:

import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;

// ...

Sort sort = new Sort(new SortField("sorting_name", SortField.STRING));
hibernateQuery.setSort(sort); // a FullTextQuery object

運行此查詢後,得到的結果會依照App名字,從A-Z進行排序。 實際上。SortField還可以接受第三個boolean類型的參數,當傳入true時,排序結果會被顛倒即從Z-A。

分頁(Pagination)

當搜索會返回大量結果時,通常都不可能將它們一次性返回。而是使用分頁技術一次僅僅返回並顯示一部分數據。

對於Hibernate Search的FullTextQuery對象。能夠使用例如以下代碼完畢分頁:

hibernateQuery.setFirstResult(10);
hibernateQuery.setMaxResults(5);
List<App> apps = hibernateQuery.list();

setFirstResult指定的是偏移量。它一般是通過 頁碼(從0開始) * 一頁中的記錄數 計算得到。

比方以上代碼中的10實際上就是 2 * 5,因此它透露出來的信息是:顯示第3頁的5條數據。

而為了得到查詢的結果數量,能夠通過getResultSize方法獲得:

int resultSize = hibernateQuery.getResultSize();

在使用getResultSize方法時,不涉及到不論什麽的數據庫操作。它只通過Lucene索引來得到結果。



[Hibernate Search] (3) 基礎查詢