[Hibernate Search] (3) 基礎查詢

基礎查詢

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

映射API和查詢API

對於映射API。咱們可以經過使用Hibernate提供的註解來完畢映射工做。同一時候咱們也可以使用JPA提供的註解來完畢。相似的,對於查詢API,咱們也可以從Hibernate和JPA提供的查詢API中進行選擇。git

每種方式都有它的長處和缺點,比方當咱們使用Hibernate提供的查詢API時,意味着可以使用不少其它的特性,畢竟Hibernate Search就是創建在Hibernate之上的。而當咱們選擇JPA的查詢API時,意味着應用可以更方便的切換ORM的實現。比方咱們想將Hibernate替換成EclipseLink。github

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等。同一時候,一些設計模式如建造者模式也大量地使用了這樣的技術。apache

關鍵字查詢(Keyword Query)

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

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

下圖反映了在建立keyword查詢時可能的流程:設計模式


反映到代碼中是這種:api

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

onFields方法可以看作是多個onField方法的組合,爲了方便一次性地聲明所有查詢域。 假設onFields中接受的某個域在相應實體的索引中不存在相關信息,那麼查詢會報錯。因此,需要確保傳入到onFields方法中的域確實是存在於實體的索引中的。app

對於matching方法,一般而言它需要接受的是一個字符串對象,表示查詢的keyword。但是實際上藉助FieldBridge,傳入到該方法的參數可以是隨意類型。在「高級映射」一文中會對FieldBridge進行介紹。post

對於傳入的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」進行單獨保存。這樣作可讓查詢更具效率,但是當咱們需要對該域進行排序時,分詞器是不需要的。

所以,咱們可以對該域設置兩個@Field註解:

@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索引來獲得結果。

相關文章
相關標籤/搜索