https://www.jianshu.com/p/66b91bec12e3
elasticsearch——Rest Client
elasticsearch版本迭代太快,項目中用的5.X版本,java client使用了新推出的Rest Client。而網上的client大多是還是TransportClient。組里封裝的EsClient不滿足業務需求,只能自己研究下Rest Client。Rest Client為推薦使用,TransportClient將在未來版本中廢棄。新版本的很多api都發生了變化,改名了或者換地方了。。。
這里提供一個elasticsearch5.6 java rest client的中文文檔:https://legacy.gitbook.com/book/quanke/elasticsearch-java-rest/details。本文章只是簡單應用,詳細細節參考此文檔。
介紹
java rest client有兩個實現類,分別是RestClient和RestHighLevelClient。前者是一個低級客戶端,通過Http與elasticsearch集群進行通信,可以做到 負載均衡、故障轉移、持久化鏈接、自動發現集群節點等功能,同時支持所有elasticsearch版本,但是需要自己對請求和相應做編解碼(自己寫JSON);后者是一個高級客戶端,對增刪改差進行了封裝,不需要處理編解碼,類似之前的TransportClient,但是兼容性較差,對客戶端和集群版本要求較高。
因為RestClient沒有提供增刪改差方法,只能自己寫json並選擇Http請求的方法進行實現,一般使用較少,只有RestHighLevelClient無法滿足的情況下才會使用。這里主要介紹RestHighLevelClient。
RestHighLevelClient
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(restClient);
RestHighLevelClient僅僅是對RestClinet的一個封裝。支持異步請求。
search api
請求
構造查詢主要用到兩個類:SearchRequest和SearchSourceBuilder。
SearchRequest searchRequest = new SearchRequest(); //穿件SeachRequest,Without arguments this runs against all indices.
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();// 大多數的搜索參數被添加到 SearchSourceBuilder 。它為每個進入請求體的每個東西都提供 setter 方法。
searchSourceBuilder.query(QueryBuilders.matchAllQuery()); // 添加一個 match_all 查詢到 searchSourceBuilder 。
searchRequest.source(searchSourceBuilder); //將searchSourceBuilder添加到searchRequest
這里構造了一個查詢請求,內容為一個match_all查詢。
SearchRequest為最終的查詢請求封裝,而查詢的大部分細節由SearchSourceBuilder指定。
首先來看一下常用的幾個SearchRequest方法
SearchRequest searchRequest = new SearchRequest("posts"); // 限制請求到某個索引上
searchRequest.indices("gets"); //設定索引
searchRequest.types("doc"); // 限制請求的類別
Es-java包中請求體封裝類,其屬性的setter和getter方法,都沒有相應的set和get標記,直接使用屬性名作為方法名,然后通過入參和返回值區分哪個是setter哪個是getter。之前版本中用的標准setter和getter在(至少在5.X)客戶端中已經不見了。
還有很多其他的配置參數,這里不再羅列。
然后是搜索細節——SearchSourceBuilder
SearchSourceBuilder可以配置大多數的搜索細節,以下為幾個實例,詳細參考文檔:
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //使用默認選項創建 SearchSourceBuilder 。 sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));//設置查詢對象。可以使任何類型的 QueryBuilder sourceBuilder.from(0); //設置from選項,確定要開始搜索的結果索引。 默認為0。 sourceBuilder.size(5); //設置大小選項,確定要返回的搜索匹配數。 默認為10。 sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); //設置一個可選的超時時間,用於控制搜索允許的時間。
sourceBuilder.query:設置查詢對象。所有Query DSL支持的搜索類型都有對應的QueryBuilder。創建QueryBuilder有兩種方式:直接new和QueryBuilders工廠方法,我習慣后者(實際沒差別)。常見的QueryBuilder列幾個:
// 創建一個字段“user”與文本“kimchy”相匹配的的全文匹配查詢。
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("user", "kimchy");
// 創建一個字段date的范圍查詢。
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("date").lte(time);
每個QueryBuilder都根據各自的查詢提供了對應的方法,同時支持鏈式創建並初始化。
QueryBuilder支持像curl一樣進行查詢嵌套,比如一個bool的must里套一個bool,should里放一個bool一個range。
無論怎么嵌套,最終得到的QueryBuilder結構就像傳統的search json一樣。最后把這個QueryBuilder添加到SearchSourceBuilder中:
searchSourceBuilder.query(queryBuilder);
每個SearchSourceBuilder只能設定一個QueryBuilder,即一個Query DSL中只有一個query元素。
在傳統Query DSL中,還有一些在結構上與query並列的,比如sort,在java中表示如下:
FieldSortBuilder sortBuilder = SortBuilders.fieldSort(sortField).order(sortOrder);
searchSourceBuilder.sort(sortBuilder);
可以控制一次返回文檔的個數、返回文檔的字段:
searchSourceBuilder.size(num); searchSourceBuilder.fetchSource(includeFields, excludeFields);
實現高亮:
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
HighlightBuilder highlightBuilder = new HighlightBuilder(); //創建一個HighlightBuilder
HighlightBuilder.Field highlightTitle =new HighlightBuilder.Field("title"); //指定高亮字段1
highlightBuilder.field(highlightTitle); //指定高亮字段2
highlightBuilder.preTags(preTags); //修改高亮前綴(默認http標簽)
highlightBuilder.postTags(postTags); //修改高亮后綴(默認http標簽)
searchSourceBuilder.highlighter(highlightBuilder);
然后可以從結果中獲取高亮顯示的文本字段。
實現聚合:
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_company").field("company.keyword");
aggregation.subAggregation(AggregationBuilders.avg("average_age").field("age"));
searchSourceBuilder.aggregation(aggregation);
詳細方法參考聚合api。
響應
SearchResponse searchResponse = client.search(searchRequest);
通過SearchResponse獲得所有的相應數據。
無論請求還是相應,想要構造或者解析都需要按照DSL的格式,一層一層去做。
可以遍歷返回的文檔:
SearchHits hits = searchResponse.getHits(); //獲得hits數組 long totalHits = hits.getTotalHits(); //獲取檢索的文檔總數(不是這次返回的數量) for (SearchHit hit : hits) { String index = hit.getIndex(); //獲取文檔的index String type = hit.getType(); //獲取文檔的type String id = hit.getId(); //獲取文檔的id Map<String, Object> sourceMap = hit.getSource(); //獲取文檔內容,封裝為map String sourceString = hit.getSourceAsString(); //獲取文檔內容,轉換為json字符串。 }
這里遍歷,官方使用了
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// do something with the SearchHit
}
其實SearchHits實現了Iterable接口,可以直接進行迭代。
hit中除了source之外的字段,也可以進行獲取,以高亮為例:
Map<String, HighlightField> highlightFields = hit.getHighlightFields(); HighlightField highlight = highlightFields.get("title"); //獲取該title領域 的突出顯示 Text[] fragments = highlight.fragments(); //獲取包含突出顯示的字段內容的一個或多個片段 String fragmentString = fragments[0].string();
聚合的話不在Hits中,需要從SearchResponse中獲取Aggregations對象:
Aggregations aggregations = searchResponse.getAggregations(); Terms byCompanyAggregation = aggregations.get("by_company"); //Get the by_company terms aggregation Bucket elasticBucket = byCompanyAggregation.getBucketByKey("Elastic"); // Avg averageAge = elasticBucket.getAggregations().get("average_age"); //Get the average_age sub-aggregation from that bucket double avg = averageAge.getValue();
其他參考官方API。
游標
5.X版本中的游標使用也跟之前有了不同,下面簡單介紹:
final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L)); // 創建游標對象 SearchRequest searchRequest = new SearchRequest("posts"); searchRequest.scroll(scroll); // 為查詢設置游標,注意是在SearchRequest中配置 SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(matchQuery("title", "Elasticsearch")); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = client.search(searchRequest); //通過發送初始化 SearchRequest 來初始化搜索上下文 String scrollId = searchResponse.getScrollId(); // 獲取scrollId SearchHit[] searchHits = searchResponse.getHits().getHits(); while (searchHits != null && searchHits.length > 0) { //在一個循環中通過調用 Search Scroll api 檢索所有搜索命中結果,知道沒有文檔返回為止。 //創建一個新的SearchScrollRequest,持有最近一次返回的滾動標識符和滾動間隔 SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); scrollRequest.scroll(scroll); //重新設置游標ID searchResponse = client.searchScroll(scrollRequest); //游標查詢,這里使用searchScroll,與第一次查詢不同 scrollId = searchResponse.getScrollId(); //獲取新的游標ID searchHits = searchResponse.getHits().getHits(); //處理返回的搜索結果 } ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); //一旦查詢全部完成,清除游標 clearScrollRequest.addScrollId(scrollId); ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest); boolean succeeded = clearScrollResponse.isSucceeded();
delete api
在RestHighLevelClient中,刪除只支持按照index/type/id進行精確刪除,不支持delete by query的操作。普通的TransportClient支持delete by query。
DeleteRequest request = new DeleteRequest(indexName, type, id); //創建DeleteRequest對象 DeleteResponse response = client.delete(request); //執行刪除操作
DeleteRequest除了以上三個值,還有很多可選參數:
request.routing("routing"); // 路由值 request.parent("parent"); //Parent 值 request.timeout(TimeValue.timeValueMinutes(2)); // TimeValue 類型的等待主分片可用的超時時間 request.timeout("2m"); // 字符串類型的等待主分片可用的超時時間 request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);// Refresh policy as a WriteRequest.RefreshPolicy instance request.setRefreshPolicy("wait_for"); // Refresh policy as a String request.version(2); // Version request.versionType(VersionType.EXTERNAL); // Version type
相應DeleteResponse可以獲得操作結果的各種信息:
String index = deleteResponse.getIndex(); // index String type = deleteResponse.getType(); // type String id = deleteResponse.getId(); // id long version = deleteResponse.getVersion(); .// version
index api
IndexRequest request = new IndexRequest(
"posts", //Index
"doc", //Type
"1"); //Document id
String jsonString = "{" +
"\"user\":\"kimchy\"," +
"\"postDate\":\"2013-01-30\"," +
"\"message\":\"trying out Elasticsearch\"" +
"}";
request.source(jsonString, XContentType.JSON); /以字符串提供的 Document source
這里使用IndexRequest做index請求。包括index\type\id信息,然后創建了index文檔,這里采用字符串方式。
文檔可以使用多種方式提供,包括字符串、Map、XContentBuilder、Object數組、BytesReference、鍵值對數組等方式:
Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put("user", "kimchy"); jsonMap.put("postDate", new Date()); jsonMap.put("message", "trying out Elasticsearch"); IndexRequest indexRequest = new IndexRequest("posts", "doc", "1").source(jsonMap); //Map 作為文檔源,它可以自動轉換為 JSON 格式。 XContentBuilder builder = XContentFactory.jsonBuilder(); builder.startObject(); { builder.field("user", "kimchy"); builder.field("postDate", new Date()); builder.field("message", "trying out Elasticsearch"); } builder.endObject(); IndexRequest indexRequest = new IndexRequest("posts", "doc", "1").source(builder); //XContentBuilder 對象作為文檔源,由 Elasticsearch 內置的幫助器生成 JSON 內容 IndexRequest indexRequest = new IndexRequest("posts", "doc", "1") .source("user", "kimchy", "postDate", new Date(), "message", "trying out Elasticsearch"); //以鍵值對對象作為文檔來源,它自動轉換為 JSON 格式
other api
待續。。。(各種api以官方rest client文檔為准)