Spring Boot操作ES進(jìn)行各種高級(jí)查詢(值得收藏)

作者 | 后青春期的Keats
創(chuàng)建SpringBoot項(xiàng)目,導(dǎo)入 ES 6.2.1 的 RestClient 依賴和 ES 依賴。在項(xiàng)目中直接引用 es-starter 的話會(huì)報(bào)容器初始化異常錯(cuò)誤,導(dǎo)致項(xiàng)目無(wú)法啟動(dòng)。如果有讀者解決了這個(gè)問(wèn)題,歡迎留言交流
<!-- ES 客戶端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
<!-- ES 版本 -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
為容器定義 RestClient 對(duì)象
/**
* 在Spring容器中定義 RestClient 對(duì)象
* @Author: keats_coder
* @Date: 2019/8/9
* @Version 1.0
* */
@Configuration
public class ESConfig {
@Value("${yunshangxue.elasticsearch.hostlist}")
private String hostlist; // 127.0.0.1:9200
@Bean // 高版本客戶端
public RestHighLevelClient restHighLevelClient() {
// 解析 hostlist 配置信息。假如以后有多個(gè),則需要用 , 分開(kāi)
String[] split = hostlist.split(",");
// 創(chuàng)建 HttpHost 數(shù)組,其中存放es主機(jī)和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for (int i = 0; i < split.length; i++) {
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
// 創(chuàng)建RestHighLevelClient客戶端
return new RestHighLevelClient(RestClient.builder(httpHostArray));
}
// 項(xiàng)目主要使用 RestHighLevelClient,對(duì)于低級(jí)的客戶端暫時(shí)不用
@Bean
public RestClient restClient() {
// 解析hostlist配置信息
String[] split = hostlist.split(",");
// 創(chuàng)建HttpHost數(shù)組,其中存放es主機(jī)和端口的配置信息
HttpHost[] httpHostArray = new HttpHost[split.length];
for (int i = 0; i < split.length; i++) {
String item = split[i];
httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
}
return RestClient.builder(httpHostArray).build();
}
}
在 yml 文件中配置 eshost
yunshangxue:
elasticsearch:
hostlist: ${eshostlist:127.0.0.1:9200}
調(diào)用相關(guān) API 執(zhí)行操作
創(chuàng)建操作索引的對(duì)象 構(gòu)建操作索引的請(qǐng)求 調(diào)用對(duì)象的相關(guān)API發(fā)送請(qǐng)求 獲取響應(yīng)消息
/**
* 刪除索引庫(kù)
*/
@Test
public void testDelIndex() throws IOException {
// 操作索引的對(duì)象
IndicesClient indices = client.indices();
// 刪除索引的請(qǐng)求
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("ysx_course");
// 刪除索引
DeleteIndexResponse response = indices.delete(deleteIndexRequest);
// 得到響應(yīng)
boolean b = response.isAcknowledged();
System.out.println(b);
}
創(chuàng)建索引, 步驟和刪除類似,需要注意的是刪除的時(shí)候需要指定 ES 庫(kù)分片的數(shù)量和副本的數(shù)量,并且在創(chuàng)建索引的時(shí)候可以將映射一起指定了。代碼如下
public void testAddIndex() throws IOException {
// 操作索引的對(duì)象
IndicesClient indices = client.indices();
// 創(chuàng)建索引的請(qǐng)求
CreateIndexRequest request = new CreateIndexRequest("ysx_course");
request.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
// 創(chuàng)建映射
request.mapping("doc", "{\n" +
" \"properties\": {\n" +
" \"description\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"search_analyzer\": \"ik_smart\"\n" +
" },\n" +
"\"pic\":{ \n" +
"\"type\":\"text\", \n" +
"\"index\":false \n" +
"}, \n" +
" \"price\": {\n" +
" \"type\": \"float\"\n" +
" },\n" +
" \"studymodel\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"timestamp\": {\n" +
" \"type\": \"date\",\n" +
" \"format\": \"yyyy-MM‐dd HH:mm:ss||yyyy‐MM‐dd||epoch_millis\"\n" +
" }\n" +
" }\n" +
" }", XContentType.JSON);
// 執(zhí)行創(chuàng)建操作
CreateIndexResponse response = indices.create(request);
// 得到響應(yīng)
boolean b = response.isAcknowledged();
System.out.println(b);
}
Java API操作ES
準(zhǔn)備數(shù)據(jù)環(huán)境
創(chuàng)建索引:ysx_course
創(chuàng)建映射:
PUT http://localhost:9200/ysx_course/doc/_mapping
{
"properties": {
"description": { // 課程描述
"type": "text", // String text 類型
"analyzer": "ik_max_word", // 存入的分詞模式:細(xì)粒度
"search_analyzer": "ik_smart" // 查詢的分詞模式:粗粒度
},
"name": { // 課程名稱
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"pic":{ // 圖片地址
"type":"text",
"index":false // 地址不用來(lái)搜索,因此不為它構(gòu)建索引
},
"price": { // 價(jià)格
"type": "scaled_float", // 有比例浮點(diǎn)
"scaling_factor": 100 // 比例因子 100
},
"studymodel": {
"type": "keyword" // 不分詞,全關(guān)鍵字匹配
},
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
加入原始數(shù)據(jù):
POST http://localhost:9200/ysx_course/doc/1
{
"name": "Bootstrap開(kāi)發(fā)",
"description": "Bootstrap是由Twitter推出的一個(gè)前臺(tái)頁(yè)面開(kāi)發(fā)框架,是一個(gè)非常流行的開(kāi)發(fā)框架,此框架集成了多種頁(yè)面效果。此開(kāi)發(fā)框架包含了大量的CSS、JS程序代碼,可以幫助開(kāi)發(fā)者(尤其是不擅長(zhǎng)頁(yè)面開(kāi)發(fā)的程序人員)輕松的實(shí)現(xiàn)一個(gè)不受瀏覽器限制的精美界面效果。",
"studymodel": "201002",
"price":38.6,
"timestamp":"2018-04-25 19:11:35",
"pic":"group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
DSL搜索
DSL(Domain Specific Language)是ES提出的基于json的搜索方式,在搜索時(shí)傳入特定的json格式的數(shù)據(jù)來(lái)完成不 同的搜索需求。DSL比URI搜索方式功能強(qiáng)大,在項(xiàng)目中建議使用DSL方式來(lái)完成搜索。
查詢?nèi)?/h5>
原本我們想要查詢?nèi)康脑?,需要使?GET 請(qǐng)求發(fā)送 _search 命令,如今使用 DSL 方式搜索,可以使用 POST 請(qǐng)求,并在請(qǐng)求體中設(shè)置 JSON 字符串來(lái)構(gòu)建查詢條件
POST http://localhost:9200/ysx_course/doc/_search
請(qǐng)求體 JSON
{
"query": {
"match_all": {} // 查詢?nèi)?/span>
},
"_source" : ["name","studymodel"] // 查詢結(jié)果包括 課程名 + 學(xué)習(xí)模式兩個(gè)映射
}
具體的測(cè)試方法如下:過(guò)程比較繁瑣,好在條理還比較清晰
// 搜索全部記錄
@Test
public void testSearchAll() throws IOException, ParseException {
// 搜索請(qǐng)求對(duì)象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定類型
searchRequest.types("doc");
// 搜索源構(gòu)建對(duì)象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
// 設(shè)置源字段過(guò)慮,第一個(gè)參數(shù)結(jié)果集包括哪些字段,第二個(gè)參數(shù)表示結(jié)果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
// 向搜索請(qǐng)求對(duì)象中設(shè)置搜索源
searchRequest.source(searchSourceBuilder);
// 執(zhí)行搜索,向ES發(fā)起http請(qǐng)求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索結(jié)果
SearchHits hits = searchResponse.getHits();
// 匹配到的總記錄數(shù)
long totalHits = hits.getTotalHits();
// 得到匹配度高的文檔
SearchHit[] searchHits = hits.getHits();
// 日期格式化對(duì)象
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for(SearchHit hit:searchHits){
// 文檔的主鍵
String id = hit.getId();
// 源文檔內(nèi)容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
// 由于前邊設(shè)置了源文檔字段過(guò)慮,這時(shí)description是取不到的
String description = (String) sourceAsMap.get("description");
// 學(xué)習(xí)模式
String studymodel = (String) sourceAsMap.get("studymodel");
// 價(jià)格
Double price = (Double) sourceAsMap.get("price");
// 日期
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
System.out.println(name);
System.out.println(studymodel);
System.out.println("你看不見(jiàn)我,看不見(jiàn)我~" + description);
System.out.println(price);
}
}
坑:red>
執(zhí)行過(guò)程中遇到的問(wèn)題:不能對(duì)這個(gè)值進(jìn)行初始化,導(dǎo)致 Spring 容器無(wú)法初始化
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'yunshangxue.elasticsearch.hostlist' in value "${yunshangxue.elasticsearch.hostlist}"
通過(guò)檢查 target 目錄發(fā)現(xiàn),生成的 target 文件包中沒(méi)有將 yml 配置文件帶過(guò)來(lái)... 仔細(xì)對(duì)比發(fā)現(xiàn),我的項(xiàng)目竟然變成了一個(gè)不是 Maven 的項(xiàng)目。重新使用 IDEA 導(dǎo)入 Mavaen 工程之后便能正常運(yùn)行了
分頁(yè)查詢
我們來(lái) look 一下 ES 的分頁(yè)查詢參數(shù):
{
// from 起始索引
// size 每頁(yè)顯示的條數(shù)
"from" : 0, "size" : 1,
"query": {
"match_all": {}
},
"_source" : ["name","studymodel"]
}
堅(jiān)決不給中國(guó)人發(fā)Offer的GitLab成立中國(guó)公司!立志3-5年上市,怕是聞到了韭菜香?
通過(guò)查詢結(jié)果可以發(fā)現(xiàn),我們?cè)O(shè)置了分頁(yè)參數(shù)之后, hits.total 仍然是 3,表示它找到了 3 條數(shù)據(jù),而按照分頁(yè)規(guī)則,它只會(huì)返回一條數(shù)據(jù),因此 hits.hits 里面只有一條數(shù)據(jù)。這也符合我們的業(yè)務(wù)規(guī)則,在查詢前端頁(yè)面顯示總共的條數(shù)和當(dāng)前的數(shù)據(jù)。
由此,我們就可以通過(guò) Java API 來(lái)構(gòu)建查詢條件了:對(duì)上面查詢?nèi)康拇a進(jìn)行如下改造:
// 搜索源構(gòu)建對(duì)象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
int page = 2; // 頁(yè)碼
int size = 1; // 每頁(yè)顯示的條數(shù)
int index = (page - 1) * size;
searchSourceBuilder.from(index);
searchSourceBuilder.size(1);
// 搜索方式
// matchAllQuery搜索全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
精確查詢 TermQuery
Term Query為精確查詢,在搜索時(shí)會(huì)整體匹配關(guān)鍵字,不再將關(guān)鍵字分詞
例如:
{
"query": {
"term": { // 查詢的方式為 term 精確查詢
"name": "spring" // 查詢的字段為 name 關(guān)鍵字是 spring
}
},
"_source": [
"name",
"studymodel"
]
}
此時(shí)查詢的結(jié)果是:
"hits": [
{
"_index": "ysx_course",
"_type": "doc",
"_id": "3",
"_score": 0.9331132,
"_source": {
"studymodel": "201001",
"name": "spring開(kāi)發(fā)基礎(chǔ)"
}
}
]
查詢到了上面這條數(shù)據(jù),因?yàn)?spring開(kāi)發(fā)基礎(chǔ) 分完詞后是 spring 開(kāi)發(fā) 基礎(chǔ) ,而查詢關(guān)鍵字是 spring 不分詞,這樣當(dāng)然可以匹配到這條記錄,但是當(dāng)我們修改關(guān)鍵字為 spring開(kāi)發(fā),按照往常的查詢方法,也是可以查詢到的。但是 term 不一樣,它不會(huì)對(duì)關(guān)鍵字分詞。結(jié)果可想而知是查詢不到的
JavaAPI如下:
// 搜索方式
// termQuery 精確查詢
searchSourceBuilder.query(QueryBuilders.termQuery("studymodel", "201002"));
根據(jù) ID 查詢:
根據(jù) ID 精確查詢和根據(jù)其他條件精確查詢是一樣的,不同的是 id 字段前面有一個(gè)下劃線注意寫(xiě)上
searchSourceBuilder.query(QueryBuilders.termQuery("_id", "1"));
但是,當(dāng)一次查詢多個(gè) ID 時(shí),相應(yīng)的 API 也應(yīng)該改變,使用 termsQuery 而不是 termQuery。多了一個(gè) s
全文檢索 MatchQuery
MatchQuery 即全文檢索,會(huì)對(duì)關(guān)鍵字進(jìn)行分詞后匹配詞條。
query:搜索的關(guān)鍵字,對(duì)于英文關(guān)鍵字如果有多個(gè)單詞則中間要用半角逗號(hào)分隔,而對(duì)于中文關(guān)鍵字中間可以用 逗號(hào)分隔也可以不用
operator:設(shè)置查詢的結(jié)果取交集還是并集,并集用 or, 交集用 and
{
"query": {
"match": {
"description": {
"query": "spring開(kāi)發(fā)",
"operator": "or"
}
}
}
}
有時(shí),我們需要設(shè)定一個(gè)量化的表達(dá)方式,例如查詢 spring開(kāi)發(fā)基礎(chǔ),這三個(gè)詞條。我們需求是至少匹配兩個(gè)詞條,這時(shí) operator 屬性就不能滿足要求了,ES 還提供了另外一個(gè)屬性:minimum_should_match 用一個(gè)百分?jǐn)?shù)來(lái)設(shè)定應(yīng)該有多少個(gè)詞條滿足要求。例如查詢:
“spring開(kāi)發(fā)框架”會(huì)被分為三個(gè)詞:spring、開(kāi)發(fā)、框架 設(shè)置"minimum_should_match": "80%"表示,三個(gè)詞在文檔的匹配占比為80%,即3*0.8=2.4,向下取整得2,表 示至少有兩個(gè)詞在文檔中要匹配成功。
JavaAPI
通過(guò) matchQuery.minimumShouldMatch 的方式來(lái)設(shè)置條件
// matchQuery全文檢索
searchSourceBuilder.query(QueryBuilders.matchQuery("description", "Spring開(kāi)發(fā)框架").minimumShouldMatch("70%"));
多字段聯(lián)合搜索 MultiQuery
上面的 MatchQuery 有一個(gè)短板,假如用戶輸入了某關(guān)鍵字,我們?cè)诓檎业臅r(shí)候并不知道他輸入的是 name 還是 description,這時(shí)我們用什么都不合適,而 MultiQuery 的出現(xiàn)解決了這個(gè)問(wèn)題,他可以通過(guò) fields 屬性來(lái)設(shè)置多個(gè)域聯(lián)合查找:具體用法如下
{
"query": {
"multi_match": {
"query": "Spring開(kāi)發(fā)",
"minimum_should_match": "70%",
"fields": ["name", "description"]
}
}
}
JavaAPI
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring開(kāi)發(fā)框架", "name", "description").minimumShouldMatch("70%"));
提升 boost
在多域聯(lián)合查詢的時(shí)候,可以通過(guò) boost 來(lái)設(shè)置某個(gè)域在計(jì)算得分時(shí)候的比重,比重越高的域當(dāng)他符合條件時(shí)計(jì)算的得分越高,相應(yīng)的該記錄也更靠前。通過(guò)在 fields 中給相應(yīng)的字段用 ^權(quán)重倍數(shù)來(lái)實(shí)現(xiàn)
"fields": ["name^10", "description"]
上面的代碼表示給 name 字段提升十倍權(quán)重,查詢到的結(jié)果:
{
"_index": "ysx_course",
"_type": "doc",
"_id": "3",
"_score": 13.802518, // 可以清楚的發(fā)現(xiàn),得分竟然是 13 了
"_source": {
"name": "spring開(kāi)發(fā)基礎(chǔ)",
"description": "spring 在java領(lǐng)域非常流行,java程序員都在用。",
"studymodel": "201001",
"price": 88.6,
"timestamp": "2018-02-24 19:11:35",
"pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
}
},
而在 Java 中,仍然可以通過(guò)鏈?zhǔn)骄幊虂?lái)實(shí)現(xiàn)
searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring開(kāi)發(fā)框架", "name", "description").field("name", 10)); // 設(shè)置 name 10倍權(quán)重
布爾查詢 BoolQuery
如果我們既要對(duì)一些字段進(jìn)行分詞查詢,同時(shí)要對(duì)另一些字段進(jìn)行精確查詢,就需要使用布爾查詢來(lái)實(shí)現(xiàn)了。布爾查詢對(duì)應(yīng)于Lucene的BooleanQuery查詢,實(shí)現(xiàn)將多個(gè)查詢組合起來(lái),有三個(gè)可選的參數(shù):
must:文檔必須匹配must所包括的查詢條件,相當(dāng)于 “AND”
should:文檔應(yīng)該匹配should所包括的查詢條件其中的一個(gè)或多個(gè),相當(dāng)于 "OR"
must_not:文檔不能匹配must_not所包括的該查詢條件,相當(dāng)于“NOT”
{
"query": {
"bool": { // 布爾查詢
"must": [ // 查詢條件 must 表示數(shù)組中的查詢方式所規(guī)定的條件都必須滿足
{
"multi_match": {
"query": "spring框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
]
}
},
{
"term": {
"studymodel": "201001"
}
}
]
}
}
}
JavaAPI
// 搜索方式
// 首先構(gòu)造多關(guān)鍵字查詢條件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring開(kāi)發(fā)框架", "name", "description").field("name", 10);
// 然后構(gòu)造精確匹配查詢條件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("studymodel", "201002");
// 組合兩個(gè)條件,組合方式為 must 全滿足
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
boolQueryBuilder.must(termQueryBuilder);
// 將查詢條件封裝給查詢對(duì)象
searchSourceBuilder.query(boolQueryBuilder);
過(guò)濾器
定義過(guò)濾器查詢,是在原本查詢結(jié)果的基礎(chǔ)上對(duì)數(shù)據(jù)進(jìn)行篩選,因此省略了重新計(jì)算的分的步驟,效率更高。并且方便緩存。推薦盡量使用過(guò)慮器去實(shí)現(xiàn)查詢或者過(guò)慮器和查詢共同使用,過(guò)濾器在布爾查詢中使用,下邊是在搜索結(jié)果的基礎(chǔ)上進(jìn)行過(guò)濾:
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "spring框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
]
}
}
],
"filter": [
{
// 過(guò)濾條件:studymodel 必須是 201001
"term": {"studymodel": "201001"}
},
{
// 過(guò)濾條件:價(jià)格 >=60 <=100
"range": {"price": {"gte": 60,"lte": 100}}
}
]
}
}
}
注意:range和term一次只能對(duì)一個(gè)Field設(shè)置范圍過(guò)慮。
JavaAPI
// 首先構(gòu)造多關(guān)鍵字查詢條件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
// 添加條件到布爾查詢
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
// 通過(guò)布爾查詢來(lái)構(gòu)造過(guò)濾查詢
boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
// 將查詢條件封裝給查詢對(duì)象
searchSourceBuilder.query(boolQueryBuilder);
排序
我們可以在查詢的結(jié)果上進(jìn)行二次排序,支持對(duì) keyword、date、float 等類型添加排序,text類型的字段不允許排序。排序使用的 JSON 格式如下:
{
"query": {
"bool": {
"filter": [
{
"range": {
"price": {
"gte": 0,
"lte": 100
}
}
}
]
}
},
"sort": [ // 注意這里排序是寫(xiě)在 query key 的外面的。這就表示它的API也不是布爾查詢提供
{
"studymodel": "desc" // 對(duì) studymodel(keyword)降序
},
{
"price": "asc" // 對(duì) price(double)升序
}
]
}
由上面的 JSON 數(shù)據(jù)可以發(fā)現(xiàn),排序所屬的 API 是和 query 評(píng)級(jí)的,因此在調(diào)用 API 時(shí)也應(yīng)該選擇對(duì)應(yīng)的 SearchSourceBuilder 對(duì)象
// 排序查詢
@Test
public void testSort() throws IOException, ParseException {
// 搜索請(qǐng)求對(duì)象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定類型
searchRequest.types("doc");
// 搜索源構(gòu)建對(duì)象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// 添加條件到布爾查詢
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 通過(guò)布爾查詢來(lái)構(gòu)造過(guò)濾查詢
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
// 將查詢條件封裝給查詢對(duì)象
searchSourceBuilder.query(boolQueryBuilder);
// 向搜索請(qǐng)求對(duì)象中設(shè)置搜索源
searchRequest.source(searchSourceBuilder);
// 設(shè)置排序規(guī)則
searchSourceBuilder.sort("studymodel", SortOrder.DESC); // 第一排序規(guī)則
searchSourceBuilder.sort("price", SortOrder.ASC); // 第二排序規(guī)則
// 執(zhí)行搜索,向ES發(fā)起http請(qǐng)求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索結(jié)果
SearchHits hits = searchResponse.getHits();
// 匹配到的總記錄數(shù)
long totalHits = hits.getTotalHits();
// 得到匹配度高的文檔
SearchHit[] searchHits = hits.getHits();
// 日期格式化對(duì)象
soutData(searchHits);
}
高亮顯示
高亮顯示可以將搜索結(jié)果一個(gè)或多個(gè)字突出顯示,以便向用戶展示匹配關(guān)鍵字的位置。
高亮三要素:高亮關(guān)鍵字、高亮前綴、高亮后綴
{
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "開(kāi)發(fā)框架",
"minimum_should_match": "50%",
"fields": [
"name^10",
"description"
],
"type": "best_fields"
}
}
]
}
},
"sort": [
{
"price": "asc"
}
],
"highlight": {
"pre_tags": [
"<em>"
],
"post_tags": [
"</em>"
],
"fields": {
"name": {},
"description": {}
}
}
}
查詢結(jié)果的數(shù)據(jù)如下:
這是人家大一新生開(kāi)發(fā)的工具!網(wǎng)友:我好菜
Java 代碼如下,注意到上面的 JSON 數(shù)據(jù), highlight 和 sort 和 query 依然是同級(jí)的,所以也需要用 SearchSourceBuilder 對(duì)象來(lái)設(shè)置到搜索條件中
// 高亮查詢
@Test
public void testHighLight() throws IOException, ParseException {
// 搜索請(qǐng)求對(duì)象
SearchRequest searchRequest = new SearchRequest("ysx_course");
// 指定類型
searchRequest.types("doc");
// 搜索源構(gòu)建對(duì)象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 搜索方式
// 首先構(gòu)造多關(guān)鍵字查詢條件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
// 添加條件到布爾查詢
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
// 通過(guò)布爾查詢來(lái)構(gòu)造過(guò)濾查詢
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
// 將查詢條件封裝給查詢對(duì)象
searchSourceBuilder.query(boolQueryBuilder);
// ***********************
// 高亮查詢
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.preTags("<em>"); // 高亮前綴
highlightBuilder.postTags("</em>"); // 高亮后綴
highlightBuilder.fields().add(new HighlightBuilder.Field("name")); // 高亮字段
// 添加高亮查詢條件到搜索源
searchSourceBuilder.highlighter(highlightBuilder);
// ***********************
// 設(shè)置源字段過(guò)慮,第一個(gè)參數(shù)結(jié)果集包括哪些字段,第二個(gè)參數(shù)表示結(jié)果集不包括哪些字段
searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
// 向搜索請(qǐng)求對(duì)象中設(shè)置搜索源
searchRequest.source(searchSourceBuilder);
// 執(zhí)行搜索,向ES發(fā)起http請(qǐng)求
SearchResponse searchResponse = client.search(searchRequest);
// 搜索結(jié)果
SearchHits hits = searchResponse.getHits();
// 匹配到的總記錄數(shù)
long totalHits = hits.getTotalHits();
// 得到匹配度高的文檔
SearchHit[] searchHits = hits.getHits();
// 日期格式化對(duì)象
soutData(searchHits);
}
根據(jù)查詢結(jié)果的數(shù)據(jù)結(jié)構(gòu)來(lái)獲取高亮的數(shù)據(jù),替換原有的數(shù)據(jù):
private void soutData(SearchHit[] searchHits) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (SearchHit hit : searchHits) {
// 文檔的主鍵
String id = hit.getId();
// 源文檔內(nèi)容
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String name = (String) sourceAsMap.get("name");
// 獲取高亮查詢的內(nèi)容。如果存在,則替換原來(lái)的name
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if( highlightFields != null ){
HighlightField nameField = highlightFields.get("name");
if(nameField!=null){
Text[] fragments = nameField.getFragments();
StringBuffer stringBuffer = new StringBuffer();
for (Text str : fragments) {
stringBuffer.append(str.string());
}
name = stringBuffer.toString();
}
}
// 由于前邊設(shè)置了源文檔字段過(guò)慮,這時(shí)description是取不到的
String description = (String) sourceAsMap.get("description");
// 學(xué)習(xí)模式
String studymodel = (String) sourceAsMap.get("studymodel");
// 價(jià)格
Double price = (Double) sourceAsMap.get("price");
// 日期
Date timestamp = dateFormat.parse((String) sourceAsMap.get("timestamp"));
System.out.println(name);
System.out.println(id);
System.out.println(studymodel);
System.out.println("你看不見(jiàn)我,看不見(jiàn)我~" + description);
System.out.println(price);
}
}往期推薦
