眼下咱们仅仅用到了基于keyword的查询,实际上Hibenrate Search DSL还提供了其余的查询方式,如下咱们就来一探到底。java
对于映射API。咱们可以经过使用Hibernate提供的注解来完毕映射工做。同一时候咱们也可以使用JPA提供的注解来完毕。相似的,对于查询API,咱们也可以从Hibernate和JPA提供的查询API中进行选择。git
每种方式都有它的长处和缺点,比方当咱们使用Hibernate提供的查询API时,意味着可以使用不少其它的特性,毕竟Hibernate Search就是创建在Hibernate之上的。而当咱们选择JPA的查询API时,意味着应用可以更方便的切换ORM的实现。比方咱们想将Hibernate替换成EclipseLink。github
所谓的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的查询。是最为主要的一种查询方式。眼下见到的样例都是基于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);
当咱们使用搜索引擎时,它均可以很是“聪明”地对一些输入错误进行更正。而在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方法来指定每个词语中,前多少个字符需要被排除在模糊计算中。
在通配符查询中,问号(?
)会被当作一个随意字符。而星号(*)则会被当作零个或者多个字符。
在Hibernate Search DSL中使用通配符搜索的流程例如如下:
需要使用wildcard方法来指定它是一个支持通配符的查询。
前面提到过。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();
}
范围查询的流程:
顾名思义,范围查询经过给定上限值和下限值来对某些域进行的查询。
所以。日期类型和数值类型通常会做为此类查询的目标域。
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();
假设一个查询知足不了你的需求,那么你可以使用布尔查询将若干个查询结合起来。
如下是它的流程:
使用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();
默认状况下,查询结果应该依照其和查询条件间的相关度进行排序。关于相关度排序,会在兴许的文章中介绍。
但是咱们也可以再也不使用相关度做为排序的根据,转而咱们可以使用日期,数值类型甚至字符串的顺序做为排序根据。
比方,对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。
当搜索会返回大量结果时,一般都不可能将它们一次性返回。而是使用分页技术一次仅仅返回并显示一部分数据。
对于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索引来获得结果。