[Hibernate Search] (3) 基础查询

基础查询

眼下我们仅仅用到了基于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”进行单独保存。这样做可以让查询更具效率,可是当我们须要对该域进行排序时,分词器是不须要的。

因此,我们能够对该域设置两个@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索引来得到结果。



posted @ 2017-05-20 16:10  yfceshi  阅读(1229)  评论(0编辑  收藏  举报