SpringBoot操作ES進行各種高階查詢

後青春期的Keats發表於2019-08-12

SpringBoot整合ES

建立SpringBoot專案,匯入 ES 6.2.1 的 RestClient 依賴和 ES 依賴。在專案中直接引用 es-starter 的話會報容器初始化異常錯誤,導致專案無法啟動。如果有讀者解決了這個問題,歡迎留言交流

<!-- 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 物件

/**
 * 在Spring容器中定義 RestClient 物件
 * @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 配置資訊。假如以後有多個,則需要用 , 分開
        String[] split = hostlist.split(",");
        // 建立 HttpHost 陣列,其中存放es主機和埠的配置資訊
        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");
        }
        // 建立RestHighLevelClient客戶端
        return new RestHighLevelClient(RestClient.builder(httpHostArray));
    }

    // 專案主要使用 RestHighLevelClient,對於低階的客戶端暫時不用
    @Bean
    public RestClient restClient() {
        // 解析hostlist配置資訊
        String[] split = hostlist.split(",");
        // 建立HttpHost陣列,其中存放es主機和埠的配置資訊
        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}

呼叫相關 API 執行操作

  1. 建立操作索引的物件
  2. 構建操作索引的請求
  3. 呼叫物件的相關API傳送請求
  4. 獲取響應訊息
/**
 * 刪除索引庫
 */
@Test
public void testDelIndex() throws IOException {
    // 操作索引的物件
    IndicesClient indices = client.indices();
    // 刪除索引的請求
    DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("ysx_course");
    // 刪除索引
    DeleteIndexResponse response = indices.delete(deleteIndexRequest);
    // 得到響應
    boolean b = response.isAcknowledged();
    System.out.println(b);
}

建立索引, 步驟和刪除類似,需要注意的是刪除的時候需要指定 ES 庫分片的數量和副本的數量,並且在建立索引的時候可以將對映一起指定了。程式碼如下

    public void testAddIndex() throws IOException {
        // 操作索引的物件
        IndicesClient indices = client.indices();
        // 建立索引的請求
        CreateIndexRequest request = new CreateIndexRequest("ysx_course");
        request.settings(Settings.builder().put("number_of_shards", "1").put("number_of_replicas", "0"));
        // 建立對映
        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);


        // 執行建立操作
        CreateIndexResponse response = indices.create(request);
        // 得到響應
        boolean b = response.isAcknowledged();
        System.out.println(b);
    }

Java API操作ES

準備資料環境

建立索引:ysx_course

建立對映:

PUT http://localhost:9200/ysx_course/doc/_mapping
{
    "properties": {
        "description": { // 課程描述
            "type": "text", // String text 型別
            "analyzer": "ik_max_word", // 存入的分詞模式:細粒度
            "search_analyzer": "ik_smart" // 查詢的分詞模式:粗粒度
        },
        "name": { // 課程名稱
            "type": "text",
            "analyzer": "ik_max_word",
            "search_analyzer": "ik_smart"
        },
        "pic":{ // 圖片地址
            "type":"text", 
            "index":false // 地址不用來搜尋,因此不為它構建索引
        },
        "price": { // 價格
            "type": "scaled_float", // 有比例浮點
            "scaling_factor": 100 // 比例因子 100
        },
        "studymodel": {
            "type": "keyword" // 不分詞,全關鍵字匹配
        },
        "timestamp": {
            "type": "date",
            "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
    }
}

加入原始資料:

POST http://localhost:9200/ysx_course/doc/1
{
    "name": "Bootstrap開發",
    "description": "Bootstrap是由Twitter推出的一個前臺頁面開發框架,是一個非常流行的開發框架,此框架整合了多種頁面效果。此開發框架包含了大量的CSS、JS程式程式碼,可以幫助開發者(尤其是不擅長頁面開發的程式人員)輕鬆的實現一個不受瀏覽器限制的精美介面效果。",
    "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的搜尋方式,在搜尋時傳入特定的json格式的資料來完成不
同的搜尋需求。DSL比URI搜尋方式功能強大,在專案中建議使用DSL方式來完成搜尋。

查詢全部

原本我們想要查詢全部的話,需要使用 GET 請求傳送 _search 命令,如今使用 DSL 方式搜尋,可以使用 POST 請求,並在請求體中設定 JSON 字串來構建查詢條件

POST http://localhost:9200/ysx_course/doc/_search

請求體 JSON

{ 
    "query": {
        "match_all": {} // 查詢全部
    },
    "_source" : ["name","studymodel"] // 查詢結果包括 課程名 + 學習模式兩個對映
}

具體的測試方法如下:過程比較繁瑣,好在條理還比較清晰

// 搜尋全部記錄
@Test
public void testSearchAll() throws IOException, ParseException {
    // 搜尋請求物件
    SearchRequest searchRequest = new SearchRequest("ysx_course");
    // 指定型別
    searchRequest.types("doc");
    // 搜尋源構建物件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 搜尋方式
    // matchAllQuery搜尋全部
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    // 設定源欄位過慮,第一個引數結果集包括哪些欄位,第二個參數列示結果集不包括哪些欄位
    searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
    // 向搜尋請求物件中設定搜尋源
    searchRequest.source(searchSourceBuilder);
    // 執行搜尋,向ES發起http請求
    SearchResponse searchResponse = client.search(searchRequest);
    // 搜尋結果
    SearchHits hits = searchResponse.getHits();
    // 匹配到的總記錄數
    long totalHits = hits.getTotalHits();
    // 得到匹配度高的文件
    SearchHit[] searchHits = hits.getHits();
    // 日期格式化物件
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for(SearchHit hit:searchHits){
        // 文件的主鍵
        String id = hit.getId();
        // 源文件內容
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        String name = (String) sourceAsMap.get("name");
        // 由於前邊設定了源文件欄位過慮,這時description是取不到的
        String description = (String) sourceAsMap.get("description");
        // 學習模式
        String studymodel = (String) sourceAsMap.get("studymodel");
        // 價格
        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("你看不見我,看不見我~" + description);
        System.out.println(price);
    }

}
坑:red>

執行過程中遇到的問題:不能對這個值進行初始化,導致 Spring 容器無法初始化

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'yunshangxue.elasticsearch.hostlist' in value "${yunshangxue.elasticsearch.hostlist}"

通過檢查 target 目錄發現,生成的 target 檔案包中沒有將 yml 配置檔案帶過來... 仔細對比發現,我的專案竟然變成了一個不是 Maven 的專案。重新使用 IDEA 匯入 Mavaen 工程之後便能正常執行了

分頁查詢

我們來 look 一下 ES 的分頁查詢引數:

{ 
    // from 起始索引
    // size 每頁顯示的條數
    "from" : 0, "size" : 1,
    "query": {
       "match_all": {}
     },
    "_source" : ["name","studymodel"]
}

1565524349684

通過查詢結果可以發現,我們設定了分頁引數之後, hits.total 仍然是 3,表示它找到了 3 條資料,而按照分頁規則,它只會返回一條資料,因此 hits.hits 裡面只有一條資料。這也符合我們的業務規則,在查詢前端頁面顯示總共的條數和當前的資料。

由此,我們就可以通過 Java API 來構建查詢條件了:對上面查詢全部的程式碼進行如下改造:

// 搜尋源構建物件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
int page = 2; // 頁碼
int size = 1; // 每頁顯示的條數
int index = (page - 1) * size;
searchSourceBuilder.from(index);
searchSourceBuilder.size(1);
// 搜尋方式
// matchAllQuery搜尋全部
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
精確查詢 TermQuery

Term Query為精確查詢,在搜尋時會整體匹配關鍵字,不再將關鍵字分詞

例如:

{
    "query": {
        "term": { // 查詢的方式為 term 精確查詢
            "name": "spring" // 查詢的欄位為 name 關鍵字是 spring
        }
    },
    "_source": [
        "name",
        "studymodel"
    ]
}

此時查詢的結果是:

 "hits": [
     {
         "_index": "ysx_course",
         "_type": "doc",
         "_id": "3",
         "_score": 0.9331132,
         "_source": {
             "studymodel": "201001",
             "name": "spring開發基礎"
         }
     }
 ]

查詢到了上面這條資料,因為 spring開發基礎 分完詞後是 spring 開發 基礎 ,而查詢關鍵字是 spring 不分詞,這樣當然可以匹配到這條記錄,但是當我們修改關鍵字為 spring開發,按照往常的查詢方法,也是可以查詢到的。但是 term 不一樣,它不會對關鍵字分詞。結果可想而知是查詢不到的

JavaAPI如下:

// 搜尋方式
// termQuery 精確查詢
searchSourceBuilder.query(QueryBuilders.termQuery("studymodel", "201002"));
根據 ID 查詢:

根據 ID 精確查詢和根據其他條件精確查詢是一樣的,不同的是 id 欄位前面有一個下劃線注意寫上

searchSourceBuilder.query(QueryBuilders.termQuery("_id", "1"));

但是,當一次查詢多個 ID 時,相應的 API 也應該改變,使用 termsQuery 而不是 termQuery。多了一個 s

全文檢索 MatchQuery

MatchQuery 即全文檢索,會對關鍵字進行分詞後匹配詞條。

query:搜尋的關鍵字,對於英文關鍵字如果有多個單詞則中間要用半形逗號分隔,而對於中文關鍵字中間可以用
逗號分隔也可以不用

operator:設定查詢的結果取交集還是並集,並集用 or, 交集用 and

{
    "query": {
        "match": {
            "description": {
                "query": "spring開發",
                "operator": "or"
            }
        }
    }
}

有時,我們需要設定一個量化的表達方式,例如查詢 spring開發基礎,這三個詞條。我們需求是至少匹配兩個詞條,這時 operator 屬性就不能滿足要求了,ES 還提供了另外一個屬性:minimum_should_match 用一個百分數來設定應該有多少個詞條滿足要求。例如查詢:

“spring開發框架”會被分為三個詞:spring、開發、框架
設定"minimum_should_match": "80%"表示,三個詞在文件的匹配佔比為80%,即3*0.8=2.4,向下取整得2,表
示至少有兩個詞在文件中要匹配成功。

JavaAPI

通過 matchQuery.minimumShouldMatch 的方式來設定條件

// matchQuery全文檢索
        searchSourceBuilder.query(QueryBuilders.matchQuery("description", "Spring開發框架").minimumShouldMatch("70%"));
多欄位聯合搜尋 MultiQuery

上面的 MatchQuery 有一個短板,假如使用者輸入了某關鍵字,我們在查詢的時候並不知道他輸入的是 name 還是 description,這時我們用什麼都不合適,而 MultiQuery 的出現解決了這個問題,他可以通過 fields 屬性來設定多個域聯合查詢:具體用法如下

{
    "query": {
        "multi_match": {
            "query": "Spring開發",
            "minimum_should_match": "70%",
            "fields": ["name", "description"]
        }
    }
}

JavaAPI

searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring開發框架", "name", "description").minimumShouldMatch("70%"));
提升 boost

在多域聯合查詢的時候,可以通過 boost 來設定某個域在計算得分時候的比重,比重越高的域當他符合條件時計算的得分越高,相應的該記錄也更靠前。通過在 fields 中給相應的欄位用 ^權重倍數來實現

"fields": ["name^10", "description"]

上面的程式碼表示給 name 欄位提升十倍權重,查詢到的結果:

{
    "_index": "ysx_course",
    "_type": "doc",
    "_id": "3",
    "_score": 13.802518, // 可以清楚的發現,得分竟然是 13 了
    "_source": {
        "name": "spring開發基礎",
        "description": "spring 在java領域非常流行,java程式設計師都在用。",
        "studymodel": "201001",
        "price": 88.6,
        "timestamp": "2018-02-24 19:11:35",
        "pic": "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg"
    }
},

而在 Java 中,仍然可以通過鏈式程式設計來實現

searchSourceBuilder.query(QueryBuilders.multiMatchQuery("Spring開發框架", "name", "description").field("name", 10)); // 設定 name 10倍權重
布林查詢 BoolQuery

如果我們既要對一些欄位進行分詞查詢,同時要對另一些欄位進行精確查詢,就需要使用布林查詢來實現了。布林查詢對應於Lucene的BooleanQuery查詢,實現將多個查詢組合起來,有三個可選的引數:

must:文件必須匹配must所包括的查詢條件,相當於 “AND”

should:文件應該匹配should所包括的查詢條件其中的一個或多個,相當於 "OR"

must_not:文件不能匹配must_not所包括的該查詢條件,相當於“NOT”

{
    "query": {
        "bool": { // 布林查詢
            "must": [ // 查詢條件 must 表示陣列中的查詢方式所規定的條件都必須滿足
                {
                    "multi_match": {
                        "query": "spring框架",
                        "minimum_should_match": "50%",
                        "fields": [
                            "name^10",
                            "description"
                        ]
                    }
                },
                {
                    "term": {
                        "studymodel": "201001"
                    }
                }
            ]
        }
    }
}

JavaAPI

// 搜尋方式
// 首先構造多關鍵字查詢條件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring開發框架", "name", "description").field("name", 10);
// 然後構造精確匹配查詢條件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("studymodel", "201002");
// 組合兩個條件,組合方式為 must 全滿足
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
boolQueryBuilder.must(termQueryBuilder);
// 將查詢條件封裝給查詢物件
searchSourceBuilder.query(boolQueryBuilder);
過濾器

定義過濾器查詢,是在原本查詢結果的基礎上對資料進行篩選,因此省略了重新計算的分的步驟,效率更高。並且方便快取。推薦儘量使用過慮器去實現查詢或者過慮器和查詢共同使用,過濾器在布林查詢中使用,下邊是在搜尋結果的基礎上進行過濾:

{
    "query": {
        "bool": {
            "must": [
                {
                    "multi_match": {
                        "query": "spring框架",
                        "minimum_should_match": "50%",
                        "fields": [
                            "name^10",
                            "description"
                        ]
                    }
                }
            ],
            "filter": [
                {
                    // 過濾條件:studymodel 必須是 201001
                    "term": {"studymodel": "201001"}
                },
                {
                    // 過濾條件:價格 >=60 <=100
                    "range": {"price": {"gte": 60,"lte": 100}}
                }
            ]
        }
    }
}

注意:range和term一次只能對一個Field設定範圍過慮。

JavaAPI

// 首先構造多關鍵字查詢條件
MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
// 新增條件到布林查詢
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(matchQueryBuilder);
// 通過布林查詢來構造過濾查詢
boolQueryBuilder.filter(QueryBuilders.termQuery("studymodel", "201001"));
boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
// 將查詢條件封裝給查詢物件
searchSourceBuilder.query(boolQueryBuilder);
排序

我們可以在查詢的結果上進行二次排序,支援對 keyword、date、float 等型別新增排序,text型別的欄位不允許排序。排序使用的 JSON 格式如下:

{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "price": {
                            "gte": 0,
                            "lte": 100
                        }
                    }
                }
            ]
        }
    },
    "sort": [ // 注意這裡排序是寫在 query key 的外面的。這就表示它的API也不是布林查詢提供
        {
            "studymodel": "desc" // 對 studymodel(keyword)降序
        },
        {
            "price": "asc" // 對 price(double)升序
        }
    ]
}

由上面的 JSON 資料可以發現,排序所屬的 API 是和 query 評級的,因此在呼叫 API 時也應該選擇對應的 SearchSourceBuilder 物件

// 排序查詢
@Test
public void testSort() throws IOException, ParseException {
    // 搜尋請求物件
    SearchRequest searchRequest = new SearchRequest("ysx_course");
    // 指定型別
    searchRequest.types("doc");
    // 搜尋源構建物件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 搜尋方式
    // 新增條件到布林查詢
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    // 通過布林查詢來構造過濾查詢
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(0).lte(100));
    // 將查詢條件封裝給查詢物件
    searchSourceBuilder.query(boolQueryBuilder);
    // 向搜尋請求物件中設定搜尋源
    searchRequest.source(searchSourceBuilder);
    
    // 設定排序規則
    searchSourceBuilder.sort("studymodel", SortOrder.DESC); // 第一排序規則
    searchSourceBuilder.sort("price", SortOrder.ASC); // 第二排序規則
    
    // 執行搜尋,向ES發起http請求
    SearchResponse searchResponse = client.search(searchRequest);
    // 搜尋結果
    SearchHits hits = searchResponse.getHits();
    // 匹配到的總記錄數
    long totalHits = hits.getTotalHits();
    // 得到匹配度高的文件
    SearchHit[] searchHits = hits.getHits();
    // 日期格式化物件
    soutData(searchHits);
}
高亮顯示

高亮顯示可以將搜尋結果一個或多個字突出顯示,以便向使用者展示匹配關鍵字的位置。

高亮三要素:高亮關鍵字、高亮字首、高亮字尾

{
    "query": {
        "bool": {
            "must": [
                {
                    "multi_match": {
                        "query": "開發框架",
                        "minimum_should_match": "50%",
                        "fields": [
                            "name^10",
                            "description"
                        ],
                        "type": "best_fields"
                    }
                }
            ]
        }
    },
    "sort": [
        {
            "price": "asc"
        }
    ],
    "highlight": {
        "pre_tags": [
            "<em>"
        ],
        "post_tags": [
            "</em>"
        ],
        "fields": {
            "name": {},
            "description": {}
        }
    }
}

查詢結果的資料如下:

1565585272091

Java 程式碼如下,注意到上面的 JSON 資料, highlight 和 sort 和 query 依然是同級的,所以也需要用 SearchSourceBuilder 物件來設定到搜尋條件中

// 高亮查詢
@Test
public void testHighLight() throws IOException, ParseException {
    // 搜尋請求物件
    SearchRequest searchRequest = new SearchRequest("ysx_course");
    // 指定型別
    searchRequest.types("doc");
    // 搜尋源構建物件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 搜尋方式
    // 首先構造多關鍵字查詢條件
    MultiMatchQueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery("Spring框架", "name", "description").field("name", 10);
    // 新增條件到布林查詢
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(matchQueryBuilder);
    // 通過布林查詢來構造過濾查詢
    boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(60).lte(100));
    // 將查詢條件封裝給查詢物件
    searchSourceBuilder.query(boolQueryBuilder);
    // ***********************
    
    // 高亮查詢
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<em>"); // 高亮字首
    highlightBuilder.postTags("</em>"); // 高亮字尾
    highlightBuilder.fields().add(new HighlightBuilder.Field("name")); // 高亮欄位
    // 新增高亮查詢條件到搜尋源
    searchSourceBuilder.highlighter(highlightBuilder);
    
    // ***********************
    
    // 設定源欄位過慮,第一個引數結果集包括哪些欄位,第二個參數列示結果集不包括哪些欄位
    searchSourceBuilder.fetchSource(new String[]{"name","studymodel","price","timestamp"},new String[]{});
    // 向搜尋請求物件中設定搜尋源
    searchRequest.source(searchSourceBuilder);
    // 執行搜尋,向ES發起http請求
    SearchResponse searchResponse = client.search(searchRequest);
    // 搜尋結果
    SearchHits hits = searchResponse.getHits();
    // 匹配到的總記錄數
    long totalHits = hits.getTotalHits();
    // 得到匹配度高的文件
    SearchHit[] searchHits = hits.getHits();
    // 日期格式化物件
    soutData(searchHits);
}

根據查詢結果的資料結構來獲取高亮的資料,替換原有的資料:

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();
        // 源文件內容
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();
        String name = (String) sourceAsMap.get("name");

        // 獲取高亮查詢的內容。如果存在,則替換原來的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();
            }
        }

        // 由於前邊設定了源文件欄位過慮,這時description是取不到的
        String description = (String) sourceAsMap.get("description");
        // 學習模式
        String studymodel = (String) sourceAsMap.get("studymodel");
        // 價格
        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("你看不見我,看不見我~" + description);
        System.out.println(price);
    }
}

相關文章