一、簡介
ElasticSearch和Solr都是基於Lucene的搜索引擎,不過ElasticSearch天生支持分布式,而Solr是4.0版本后的SolrCloud才是分布式版本,Solr的分布式支持需要ZooKeeper的支持。
這里有一個詳細的ElasticSearch和Solr的對比:http://solr-vs-elasticsearch.com/
語法參考:
Elasticsearch Java API
Elasticsearch Query DSL
ElasticSearch安裝部署:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0-%E4%B8%80-%E5%AE%89%E8%A3%85%E9%83%A8%E7%BD%B2/
elasticsearch Docker:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0-%E4%BA%8C-Docker/
ElasticSearch筆記:http://nero.life/2017/10/27/Elasticsearch%E7%AC%94%E8%AE%B0/
二、基本用法
集群(Cluster): ES是一個分布式的搜索引擎,一般由多台物理機組成。這些物理機,通過配置一個相同的cluster name,互相發現,把自己組織成一個集群。
節點(Node):同一個集群中的一個Elasticsearch主機。
Node類型:
1)data node: 存儲index數據。Data nodes hold data and perform data related operations such as CRUD, search, and aggregations.
2)client node: 不存儲index,處理轉發客戶端請求到Data Node。
3)master node: 不存儲index,集群管理,如管理路由信息(routing infomation),判斷node是否available,當有node出現或消失時重定位分片(shards),當有node failure時協調恢復。(所有的master node會選舉出一個master leader node)
詳情參考:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html
主分片(Primary shard):索引(下文介紹)的一個物理子集。同一個索引在物理上可以切多個分片,分布到不同的節點上。分片的實現是Lucene 中的索引。
注意:ES中一個索引的分片個數是建立索引時就要指定的,建立后不可再改變。所以開始建一個索引時,就要預計數據規模,將分片的個數分配在一個合理的范圍。
副本分片(Replica shard):每個主分片可以有一個或者多個副本,個數是用戶自己配置的。ES會盡量將同一索引的不同分片分布到不同的節點上,提高容錯性。對一個索引,只要不是所有shards所在的機器都掛了,就還能用。
索引(Index):邏輯概念,一個可檢索的文檔對象的集合。類似與DB中的database概念。同一個集群中可建立多個索引。比如,生產環境常見的一種方法,對每個月產生的數據建索引,以保證單個索引的量級可控。
類型(Type):索引的下一級概念,大概相當於數據庫中的table。同一個索引里可以包含多個 Type。
文檔(Document):即搜索引擎中的文檔概念,也是ES中一個可以被檢索的基本單位,相當於數據庫中的row,一條記錄。
字段(Field):相當於數據庫中的column。ES中,每個文檔,其實是以json形式存儲的。而一個文檔可以被視為多個字段的集合。比如一篇文章,可能包括了主題、摘要、正文、作者、時間等信息,每個信息都是一個字段,最后被整合成一個json串,落地到磁盤。
映射(Mapping):相當於數據庫中的schema,用來約束字段的類型,不過 Elasticsearch 的 mapping 可以不顯示地指定、自動根據文檔數據創建。
Database(數據庫) | Index(索引) |
Table(表) | Type(類型) |
Row(行) | Document(文檔) |
Column(列) | Field(字段) |
Schema(方案) | Mapping(映射) |
Index(索引) | Everthing Indexed by default(所有字段都被索引) |
SQL(結構化查詢語言) | Query DSL(查詢專用語言) |
Elasticsearch集群可以包含多個索引(indices),每一個索引可以包含多個類型(types),每一個類型包含多個文檔(documents),然后每個文檔包含多個字段(Fields),這種面向文檔型的儲存,也算是NoSQL的一種吧。
ES比傳統關系型數據庫,對一些概念上的理解:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
從創建一個Client到添加、刪除、查詢等基本用法:
1、創建Client
1 public ElasticSearchService(String ipAddress, int port) { 2 client = new TransportClient() 3 .addTransportAddress(new InetSocketTransportAddress(ipAddress, 4 port)); 5 }
這里是一個TransportClient。
ES下兩種客戶端對比:
TransportClient:輕量級的Client,使用Netty線程池,Socket連接到ES集群。本身不加入到集群,只作為請求的處理。
Node Client:客戶端節點本身也是ES節點,加入到集群,和其他ElasticSearch節點一樣。頻繁的開啟和關閉這類Node Clients會在集群中產生“噪音”。
2、創建/刪除Index和Type信息
1 // 創建索引 2 public void createIndex() { 3 client.admin().indices().create(new CreateIndexRequest(IndexName)) 4 .actionGet(); 5 } 6 7 // 清除所有索引 8 public void deleteIndex() { 9 IndicesExistsResponse indicesExistsResponse = client.admin().indices() 10 .exists(new IndicesExistsRequest(new String[] { IndexName })) 11 .actionGet(); 12 if (indicesExistsResponse.isExists()) { 13 client.admin().indices().delete(new DeleteIndexRequest(IndexName)) 14 .actionGet(); 15 } 16 } 17 18 // 刪除Index下的某個Type 19 public void deleteType(){ 20 client.prepareDelete().setIndex(IndexName).setType(TypeName).execute().actionGet(); 21 } 22 23 // 定義索引的映射類型 24 public void defineIndexTypeMapping() { 25 try { 26 XContentBuilder mapBuilder = XContentFactory.jsonBuilder(); 27 mapBuilder.startObject() 28 .startObject(TypeName) 29 .startObject("_all").field("enabled", false).endObject() 30 .startObject("properties") 31 .startObject(IDFieldName).field("type", "long").endObject() 32 .startObject(SeqNumFieldName).field("type", "long").endObject() 33 .startObject(IMSIFieldName).field("type", "string").field("index", "not_analyzed").endObject() 34 .startObject(IMEIFieldName).field("type", "string").field("index", "not_analyzed").endObject() 35 .startObject(DeviceIDFieldName).field("type", "string").field("index", "not_analyzed").endObject() 36 .startObject(OwnAreaFieldName).field("type", "string").field("index", "not_analyzed").endObject() 37 .startObject(TeleOperFieldName).field("type", "string").field("index", "not_analyzed").endObject() 38 .startObject(TimeFieldName).field("type", "date").field("store", "yes").endObject() 39 .endObject() 40 .endObject() 41 .endObject(); 42 43 PutMappingRequest putMappingRequest = Requests 44 .putMappingRequest(IndexName).type(TypeName) 45 .source(mapBuilder); 46 client.admin().indices().putMapping(putMappingRequest).actionGet(); 47 } catch (IOException e) { 48 log.error(e.toString()); 49 } 50 }
這里自定義了某個Type的索引映射(Mapping):
1)默認ES會自動處理數據類型的映射:針對整型映射為long,浮點數為double,字符串映射為string,時間為date,true或false為boolean。
2)字段的默認配置是indexed,但不是stored的,也就是 field("index", "yes").field("store", "no")。
3)這里Disable了“_all”字段,_all字段會把所有的字段用空格連接,然后用“analyzed”的方式index這個字段,這個字段可以被search,但是不能被retrieve。
4)針對string,ES默認會做“analyzed”處理,即先做分詞、去掉stop words等處理再index。如果你需要把一個字符串做為整體被索引到,需要把這個字段這樣設置:field("index", "not_analyzed")。
5)默認_source字段是enabled,_source字段存儲了原始Json字符串(original JSON document body that was passed at index time)。
詳情參考:
https://www.elastic.co/guide/en/elasticsearch/guide/current/mapping-intro.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-store.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-all-field.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-source-field.html
3、索引數據
1 // 批量索引數據 2 public void indexHotSpotDataList(List<Hotspotdata> dataList) { 3 if (dataList != null) { 4 int size = dataList.size(); 5 if (size > 0) { 6 BulkRequestBuilder bulkRequest = client.prepareBulk(); 7 for (int i = 0; i < size; ++i) { 8 Hotspotdata data = dataList.get(i); 9 String jsonSource = getIndexDataFromHotspotData(data); 10 if (jsonSource != null) { 11 bulkRequest.add(client 12 .prepareIndex(IndexName, TypeName, 13 data.getId().toString()) 14 .setRefresh(true).setSource(jsonSource)); 15 } 16 } 17 18 BulkResponse bulkResponse = bulkRequest.execute().actionGet(); 19 if (bulkResponse.hasFailures()) { 20 Iterator<BulkItemResponse> iter = bulkResponse.iterator(); 21 while (iter.hasNext()) { 22 BulkItemResponse itemResponse = iter.next(); 23 if (itemResponse.isFailed()) { 24 log.error(itemResponse.getFailureMessage()); 25 } 26 } 27 } 28 } 29 } 30 } 31 32 // 索引數據 33 public boolean indexHotspotData(Hotspotdata data) { 34 String jsonSource = getIndexDataFromHotspotData(data); 35 if (jsonSource != null) { 36 IndexRequestBuilder requestBuilder = client.prepareIndex(IndexName, 37 TypeName).setRefresh(true); 38 requestBuilder.setSource(jsonSource) 39 .execute().actionGet(); 40 return true; 41 } 42 43 return false; 44 } 45 46 // 得到索引字符串 47 public String getIndexDataFromHotspotData(Hotspotdata data) { 48 String jsonString = null; 49 if (data != null) { 50 try { 51 XContentBuilder jsonBuilder = XContentFactory.jsonBuilder(); 52 jsonBuilder.startObject().field(IDFieldName, data.getId()) 53 .field(SeqNumFieldName, data.getSeqNum()) 54 .field(IMSIFieldName, data.getImsi()) 55 .field(IMEIFieldName, data.getImei()) 56 .field(DeviceIDFieldName, data.getDeviceID()) 57 .field(OwnAreaFieldName, data.getOwnArea()) 58 .field(TeleOperFieldName, data.getTeleOper()) 59 .field(TimeFieldName, data.getCollectTime()) 60 .endObject(); 61 jsonString = jsonBuilder.string(); 62 } catch (IOException e) { 63 log.equals(e); 64 } 65 } 66 67 return jsonString; 68 }
ES支持批量和單個數據索引。
4、查詢獲取數據
1 // 獲取少量數據100個 2 private List<Integer> getSearchData(QueryBuilder queryBuilder) { 3 List<Integer> ids = new ArrayList<>(); 4 SearchResponse searchResponse = client.prepareSearch(IndexName) 5 .setTypes(TypeName).setQuery(queryBuilder).setSize(100) 6 .execute().actionGet(); 7 SearchHits searchHits = searchResponse.getHits(); 8 for (SearchHit searchHit : searchHits) { 9 Integer id = (Integer) searchHit.getSource().get("id"); 10 ids.add(id); 11 } 12 return ids; 13 } 14 15 // 獲取大量數據 16 private List<Integer> getSearchDataByScrolls(QueryBuilder queryBuilder) { 17 List<Integer> ids = new ArrayList<>(); 18 // 一次獲取100000數據 19 SearchResponse scrollResp = client.prepareSearch(IndexName) 20 .setSearchType(SearchType.SCAN).setScroll(new TimeValue(60000)) 21 .setQuery(queryBuilder).setSize(100000).execute().actionGet(); 22 while (true) { 23 for (SearchHit searchHit : scrollResp.getHits().getHits()) { 24 Integer id = (Integer) searchHit.getSource().get(IDFieldName); 25 ids.add(id); 26 } 27 scrollResp = client.prepareSearchScroll(scrollResp.getScrollId()) 28 .setScroll(new TimeValue(600000)).execute().actionGet(); 29 if (scrollResp.getHits().getHits().length == 0) { 30 break; 31 } 32 } 33 34 return ids; 35 }
這里的QueryBuilder是一個查詢條件,ES支持分頁查詢獲取數據,也可以一次性獲取大量數據,需要使用Scroll Search。
5、聚合(Aggregation Facet)查詢
1 // 得到某段時間內設備列表上每個設備的數據分布情況<設備ID,數量> 2 public Map<String, String> getDeviceDistributedInfo(String startTime, 3 String endTime, List<String> deviceList) { 4 5 Map<String, String> resultsMap = new HashMap<>(); 6 7 QueryBuilder deviceQueryBuilder = getDeviceQueryBuilder(deviceList); 8 QueryBuilder rangeBuilder = getDateRangeQueryBuilder(startTime, endTime); 9 QueryBuilder queryBuilder = QueryBuilders.boolQuery() 10 .must(deviceQueryBuilder).must(rangeBuilder); 11 12 TermsBuilder termsBuilder = AggregationBuilders.terms("DeviceIDAgg").size(Integer.MAX_VALUE) 13 .field(DeviceIDFieldName); 14 SearchResponse searchResponse = client.prepareSearch(IndexName) 15 .setQuery(queryBuilder).addAggregation(termsBuilder) 16 .execute().actionGet(); 17 Terms terms = searchResponse.getAggregations().get("DeviceIDAgg"); 18 if (terms != null) { 19 for (Terms.Bucket entry : terms.getBuckets()) { 20 resultsMap.put(entry.getKey(), 21 String.valueOf(entry.getDocCount())); 22 } 23 } 24 return resultsMap; 25 }
Aggregation查詢可以查詢類似統計分析這樣的功能:如某個月的數據分布情況,某類數據的最大、最小、總和、平均值等。
詳情參考:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-aggs.html
查詢准備
當我們獲取到connection以后,接下來就可以開始做查詢的准備。
-
1 首先我們可以通過client來獲取到一個SearchRequestBuilder的實例,這個是我們查詢的主干。
然后你需要給SearchRequestBuilder指定查詢目標, Index以及Type(映射到數據庫就是數據庫名稱以及表名) -
2 指定分頁信息,setFrom以及setSize。
PS: 千萬不要指望能一次性的查詢到所有的數據,尤其是document特別多的時候。Elasticsearch最多只會給你返回1000條數據,
一次性返回過量的數據是災難性,這點無論是Elasticsearch亦或是Database都適用。
1 SearchRequestBuilder searchRequestBuilder = client.prepareSearch(ES_ITEM_INDEX).setTypes(ES_ITEM_TYPE) 2 .setFrom((pageNum - 1) * pageSize) 3 .setSize(pageSize);
- 3 接下來,你可能需要指定返回的字段,可以用如下的代碼設置:
1 String[] includes = {"id", "name"}; 2 searchRequestBuilder.setFetchSource(includes, null);
- 4 構建查詢器
我們以自己熟悉的SQL來做實例講解,如下sql:
select * from student where id = '123' and age > 12 and name like '小明' and hid in (...)
我們看SQL后面where條件。包含了’等於,大於,模糊查詢,in查詢’。
首先我們需要一個能包含復雜查詢條件的BoolQueryBuilder,你可以將一個個小查詢條件設置進去,最后將它設給searchRequestBuilder。
1 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
id = ‘123’:
1 QueryBuilder idQueryBuilder = QueryBuilders.termQuery("id", 123); 2 boolQueryBuilder.must(idQueryBuilder);
age > 12:
1 RangeQueryBuilder ageQueryBuilder = QueryBuilders.rangeQuery("age").gt(12); 2 boolQueryBuilder.must(ageQueryBuilder);
name like ‘小明’:
1 MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", '小明'); 2 matchQueryBuilder.operator(Operator.AND); 3 boolQueryBuilder.must(matchQueryBuilder);
這個就是我們比較關注的關鍵字查詢了。因為我的Elasticsearch中字段name,已經配置了ik_smart分詞器。
所以此處會將我的條件”小明”進行ik_smart的分詞查詢。而我設置的Operator.AND屬性,指的是必須要我傳
入的查詢條件分詞后字段全部匹配,才會返回結果。默認是Operator.OR,也就是其中有一個匹配,結果就返回。
hid in (…):
1 QueryBuilder queryBuilder = QueryBuilders.termsQuery("hid", hidList); 2 boolQueryBuilder.must(queryBuilder);
PS: 上述的SQL條件中,皆是以AND關鍵字進行拼接。如果是其他的,比如OR,比如Not in呢?請看下面:
and – must
or – should
not in – must not
好,最后我們將queryBuilder設置進searchRequestBuilder中。
1 // 設置查詢條件 2 searchRequestBuilder.setQuery(boolQueryBuilder);
- 5 排序
我們同樣需要一個排序構造器:SortBuilder。
咱先根據age字段,做降序。如下代碼:
1 SortBuilder sortBuilder = SortBuilders.fieldSort("age"); 2 sortBuilder.order(SortOrder.fromString("DESC"));
如果想要做一些較為復雜的排序,比如多個字段相加:
1 String scriptStr = "doc['clickCount'].value + doc['soldNum'].value"; 2 sortBuilder = SortBuilders.scriptSort(new Script(scriptStr), ScriptSortBuilder.ScriptSortType.NUMBER);
1 // 設置排序規則 2 searchRequestBuilder.addSort(sortBuilder);
- 6 高亮顯示
在具體業務查詢中,其實我們通常需要用到如該圖的效果。
將查詢命中的關鍵,進行標記顏色樣式。
Elasticsearch同樣提供了功能的實現,我們只需要設置被查詢的字段即可。如下代碼:
1 HighlightBuilder highlightBuilder = new HighlightBuilder(); 2 highlightBuilder.field("name"); 3 searchRequestBuilder.highlighter(highlightBuilder);
在下面的解析結果中,會具體介紹怎么解析出這么高亮結果。
- 7 聚合
當然,Elasticsearch也提供了強大的聚合功能:
1 // 年齡聚合計算 2 TermsAggregationBuilder ageTermsAggregationBuilder = AggregationBuilders.terms("ageAgg"); 3 brandTermsAggregationBuilder.field("age"); 4 brandTermsAggregationBuilder.size(1000); 5 searchRequestBuilder.addAggregation(ageTermsAggregationBuilder);
- 8 執行查詢
1 // 開始執行查詢 2 SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
- 9 解析結果
直接上代碼:
1 SearchHits searchHits = searchResponse.getHits(); 2 List<Student> students = new ArrayList<>(); 3 for (SearchHit hit : searchHits.getHits()) { 4 String json = hit.getSourceAsString(); 5 # 讀取json,轉為成對象 6 Student student= ... 7 // 關鍵字高亮顯示 8 HighlightField highlightField = hit.getHighlightFields().get("name"); 9 String highName = student.getName(); 10 if (highlightField != null) { 11 highName = highlightField.getFragments()[0].toString(); 12 } 13 student.setHighName(highName); 14 students.add(student); 15 }
我們可以從searchResponse中,獲取到所有的命中目標列表。然后循環列表,每個命中的hit,可以直接轉化成json。
然后可以json轉化工具,映射到自己的bean上。
其中,當你設置了highlightBuilder以后,你可以在每個hit里,get到被標簽包裹着的String。
(ps: 默認是em,你也可以設置其他的標簽,以供前端渲染。)
使用QueryBuilders進行查詢
elastcisearch-query-builder接受配置文件(特定json格式)或者json格式的字符串配置,配置格式如下:
{ "index": "user_portrait", "type": "docs", "from": "${from}", "size": "10", "include_source": ["name","age"], //需要哪些字段 "exclude_source": ["sex"], //排除哪些字段 "query_type": "terms_level_query", "terms_level_query": { "terms_level_type": "term_query", "term_query": { "value": "${value}", "key": "key", "boost": 2 } }, "aggregations": [ { "aggregation_type": "terms", "name": "", "field": "field", "sub_aggregations": { "aggregation_type": "terms", "name": "sub", "field": "field", "size": "${size.value}", "sort": "asc", "sort_by": "_count" } } ], "highlight":{ "fields": [ { "field": "content", "number_of_fragment": 2, "no_match_size": 150 } ], "pre_tags":["<em>"], "post_tags":["</em>"] }, "sort": [ "_score", { "field": "age", "order": "asc" } ] }
參數說明
# index
index表示elasticSearch中的索引或者別名。
# type
type表示elasticSearch索引或者別名下的type。
# from
from表示檢索文檔時的偏移量,相當於關系型數據庫里的offset。
# include_source
include_source 搜索結果中包含某些字段,格式為json數組,"include_source": ["name","age"]
。
# exclude_source
exclude_source 搜索結果中排除某些字段,格式為json數組,"exclude_source":["sex"]
。
# query_type
query_type表示查詢類型,支持三種類型terms_level_query
,text_level_query
,bool_level_query
,並且這三種類型
不可以一起使用。
-
terms_level_query
操作的精確字段是存儲在反轉索引中的。這些查詢通常用於結構化數據,
如數字、日期和枚舉, 而不是全文字段,包含term_query,terms_query,range_query,exists_query 等類型。 -
text_level_query
查詢通常用於在完整文本字段 (如電子郵件正文) 上運行全文查詢。他們了解如何分析被查詢的字段,
並在執行之前將每個字段的分析器 (或 search_analyzer) 應用到查詢字符串。
包含 match_query,multi_match_query,query_string,simple_query_string 等類型。 -
bool_query
與其他查詢的布爾組合匹配的文檔匹配的查詢。bool 查詢映射到 Lucene BooleanQuery。它是使用一個或多個布爾子句生成的, 每個子句都有一個類型化的實例。
布爾查詢的查詢值包括: must,filter,should,must_not. 在每個布爾查詢的查詢類型值中,可以包含terms_level_query 和 text_level_query中任意的查詢類型,如此便可以構造非常復雜的查詢情況。
QueryBuilder 是es中提供的一個查詢接口, 可以對其進行參數設置來進行查詢:
1 package com.wenbronk.javaes; 2 3 import java.net.InetSocketAddress; 4 import java.util.ArrayList; 5 import java.util.Iterator; 6 import java.util.Map.Entry; 7 8 import org.elasticsearch.action.ListenableActionFuture; 9 import org.elasticsearch.action.get.GetRequestBuilder; 10 import org.elasticsearch.action.get.GetResponse; 11 import org.elasticsearch.action.search.SearchResponse; 12 import org.elasticsearch.action.search.SearchType; 13 import org.elasticsearch.client.transport.TransportClient; 14 import org.elasticsearch.common.settings.Settings; 15 import org.elasticsearch.common.text.Text; 16 import org.elasticsearch.common.transport.InetSocketTransportAddress; 17 import org.elasticsearch.common.unit.TimeValue; 18 import org.elasticsearch.index.query.IndicesQueryBuilder; 19 import org.elasticsearch.index.query.NestedQueryBuilder; 20 import org.elasticsearch.index.query.QueryBuilder; 21 import org.elasticsearch.index.query.QueryBuilders; 22 import org.elasticsearch.index.query.QueryStringQueryBuilder; 23 import org.elasticsearch.index.query.RangeQueryBuilder; 24 import org.elasticsearch.index.query.SpanFirstQueryBuilder; 25 import org.elasticsearch.index.query.WildcardQueryBuilder; 26 import org.elasticsearch.search.SearchHit; 27 import org.elasticsearch.search.SearchHits; 28 import org.junit.Before; 29 import org.junit.Test; 30 31 /** 32 * java操作查詢api 33 * @author 231 34 * 35 */ 36 public class JavaESQuery { 37 38 private TransportClient client; 39 40 @Before 41 public void testBefore() { 42 Settings settings = Settings.settingsBuilder().put("cluster.name", "wenbronk_escluster").build(); 43 client = TransportClient.builder().settings(settings).build() 44 .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress("192.168.50.37", 9300))); 45 System.out.println("success to connect escluster"); 46 } 47 48 /** 49 * 使用get查詢 50 */ 51 @Test 52 public void testGet() { 53 GetRequestBuilder requestBuilder = client.prepareGet("twitter", "tweet", "1"); 54 GetResponse response = requestBuilder.execute().actionGet(); 55 GetResponse getResponse = requestBuilder.get(); 56 ListenableActionFuture<GetResponse> execute = requestBuilder.execute(); 57 System.out.println(response.getSourceAsString()); 58 } 59 60 /** 61 * 使用QueryBuilder 62 * termQuery("key", obj) 完全匹配 63 * termsQuery("key", obj1, obj2..) 一次匹配多個值 64 * matchQuery("key", Obj) 單個匹配, field不支持通配符, 前綴具高級特性 65 * multiMatchQuery("text", "field1", "field2"..); 匹配多個字段, field有通配符忒行 66 * matchAllQuery(); 匹配所有文件 67 */ 68 @Test 69 public void testQueryBuilder() { 70 // QueryBuilder queryBuilder = QueryBuilders.termQuery("user", "kimchy"); 71 QueryBUilder queryBuilder = QueryBuilders.termQuery("user", "kimchy", "wenbronk", "vini"); 72 QueryBuilders.termsQuery("user", new ArrayList<String>().add("kimchy")); 73 // QueryBuilder queryBuilder = QueryBuilders.matchQuery("user", "kimchy"); 74 // QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("kimchy", "user", "message", "gender"); 75 QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); 76 searchFunction(queryBuilder); 77 78 } 79 80 /** 81 * 組合查詢 82 * must(QueryBuilders) : AND 83 * mustNot(QueryBuilders): NOT 84 * should: : OR 85 */ 86 @Test 87 public void testQueryBuilder2() { 88 QueryBuilder queryBuilder = QueryBuilders.boolQuery() 89 .must(QueryBuilders.termQuery("user", "kimchy")) 90 .mustNot(QueryBuilders.termQuery("message", "nihao")) 91 .should(QueryBuilders.termQuery("gender", "male")); 92 searchFunction(queryBuilder); 93 } 94 95 /** 96 * 只查詢一個id的 97 * QueryBuilders.idsQuery(String...type).ids(Collection<String> ids) 98 */ 99 @Test 100 public void testIdsQuery() { 101 QueryBuilder queryBuilder = QueryBuilders.idsQuery().ids("1"); 102 searchFunction(queryBuilder); 103 } 104 105 /** 106 * 包裹查詢, 高於設定分數, 不計算相關性 107 */ 108 @Test 109 public void testConstantScoreQuery() { 110 QueryBuilder queryBuilder = QueryBuilders.constantScoreQuery(QueryBuilders.termQuery("name", "kimchy")).boost(2.0f); 111 searchFunction(queryBuilder); 112 // 過濾查詢 113 // QueryBuilders.constantScoreQuery(FilterBuilders.termQuery("name", "kimchy")).boost(2.0f); 114 115 } 116 117 /** 118 * disMax查詢 119 * 對子查詢的結果做union, score沿用子查詢score的最大值, 120 * 廣泛用於muti-field查詢 121 */ 122 @Test 123 public void testDisMaxQuery() { 124 QueryBuilder queryBuilder = QueryBuilders.disMaxQuery() 125 .add(QueryBuilders.termQuery("user", "kimch")) // 查詢條件 126 .add(QueryBuilders.termQuery("message", "hello")) 127 .boost(1.3f) 128 .tieBreaker(0.7f); 129 searchFunction(queryBuilder); 130 } 131 132 /** 133 * 模糊查詢 134 * 不能用通配符, 不知道干啥用 135 */ 136 @Test 137 public void testFuzzyQuery() { 138 QueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("user", "kimch"); 139 searchFunction(queryBuilder); 140 } 141 142 /** 143 * 父或子的文檔查詢 144 */ 145 @Test 146 public void testChildQuery() { 147 QueryBuilder queryBuilder = QueryBuilders.hasChildQuery("sonDoc", QueryBuilders.termQuery("name", "vini")); 148 searchFunction(queryBuilder); 149 } 150 151 /** 152 * moreLikeThisQuery: 實現基於內容推薦, 支持實現一句話相似文章查詢 153 * { 154 "more_like_this" : { 155 "fields" : ["title", "content"], // 要匹配的字段, 不填默認_all 156 "like_text" : "text like this one", // 匹配的文本 157 } 158 } 159 160 percent_terms_to_match:匹配項(term)的百分比,默認是0.3 161 162 min_term_freq:一篇文檔中一個詞語至少出現次數,小於這個值的詞將被忽略,默認是2 163 164 max_query_terms:一條查詢語句中允許最多查詢詞語的個數,默認是25 165 166 stop_words:設置停止詞,匹配時會忽略停止詞 167 168 min_doc_freq:一個詞語最少在多少篇文檔中出現,小於這個值的詞會將被忽略,默認是無限制 169 170 max_doc_freq:一個詞語最多在多少篇文檔中出現,大於這個值的詞會將被忽略,默認是無限制 171 172 min_word_len:最小的詞語長度,默認是0 173 174 max_word_len:最多的詞語長度,默認無限制 175 176 boost_terms:設置詞語權重,默認是1 177 178 boost:設置查詢權重,默認是1 179 180 analyzer:設置使用的分詞器,默認是使用該字段指定的分詞器 181 */ 182 @Test 183 public void testMoreLikeThisQuery() { 184 QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery("user") 185 .like("kimchy"); 186 // .minTermFreq(1) //最少出現的次數 187 // .maxQueryTerms(12); // 最多允許查詢的詞語 188 searchFunction(queryBuilder); 189 } 190 191 /** 192 * 前綴查詢 193 */ 194 @Test 195 public void testPrefixQuery() { 196 QueryBuilder queryBuilder = QueryBuilders.matchQuery("user", "kimchy"); 197 searchFunction(queryBuilder); 198 } 199 200 /** 201 * 查詢解析查詢字符串 202 */ 203 @Test 204 public void testQueryString() { 205 QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("+kimchy"); 206 searchFunction(queryBuilder); 207 } 208 209 /** 210 * 范圍內查詢 211 */ 212 public void testRangeQuery() { 213 QueryBuilder queryBuilder = QueryBuilders.rangeQuery("user") 214 .from("kimchy") 215 .to("wenbronk") 216 .includeLower(true) // 包含上界 217 .includeUpper(true); // 包含下屆 218 searchFunction(queryBuilder); 219 } 220 221 /** 222 * 跨度查詢 223 */ 224 @Test 225 public void testSpanQueries() { 226 QueryBuilder queryBuilder1 = QueryBuilders.spanFirstQuery(QueryBuilders.spanTermQuery("name", "葫蘆580娃"), 30000); // Max查詢范圍的結束位置 227 228 QueryBuilder queryBuilder2 = QueryBuilders.spanNearQuery() 229 .clause(QueryBuilders.spanTermQuery("name", "葫蘆580娃")) // Span Term Queries 230 .clause(QueryBuilders.spanTermQuery("name", "葫蘆3812娃")) 231 .clause(QueryBuilders.spanTermQuery("name", "葫蘆7139娃")) 232 .slop(30000) // Slop factor 233 .inOrder(false) 234 .collectPayloads(false); 235 236 // Span Not 237 QueryBuilder queryBuilder3 = QueryBuilders.spanNotQuery() 238 .include(QueryBuilders.spanTermQuery("name", "葫蘆580娃")) 239 .exclude(QueryBuilders.spanTermQuery("home", "山西省太原市2552街道")); 240 241 // Span Or 242 QueryBuilder queryBuilder4 = QueryBuilders.spanOrQuery() 243 .clause(QueryBuilders.spanTermQuery("name", "葫蘆580娃")) 244 .clause(QueryBuilders.spanTermQuery("name", "葫蘆3812娃")) 245 .clause(QueryBuilders.spanTermQuery("name", "葫蘆7139娃")); 246 247 // Span Term 248 QueryBuilder queryBuilder5 = QueryBuilders.spanTermQuery("name", "葫蘆580娃"); 249 } 250 251 /** 252 * 測試子查詢 253 */ 254 @Test 255 public void testTopChildrenQuery() { 256 QueryBuilders.hasChildQuery("tweet", 257 QueryBuilders.termQuery("user", "kimchy")) 258 .scoreMode("max"); 259 } 260 261 /** 262 * 通配符查詢, 支持 * 263 * 匹配任何字符序列, 包括空 264 * 避免* 開始, 會檢索大量內容造成效率緩慢;對於通配符查詢必須注意一個問題,就是參數必須小寫,即例子中“kihy”必須小寫,這是個坑 265 */ 266 @Test 267 public void testWildCardQuery() { 268 QueryBuilder queryBuilder = QueryBuilders.wildcardQuery("user", "ki*hy"); 269 searchFunction(queryBuilder); 270 } 271 272 /** 273 * 嵌套查詢, 內嵌文檔查詢 274 */ 275 @Test 276 public void testNestedQuery() { 277 QueryBuilder queryBuilder = QueryBuilders.nestedQuery("location", 278 QueryBuilders.boolQuery() 279 .must(QueryBuilders.matchQuery("location.lat", 0.962590433140581)) 280 .must(QueryBuilders.rangeQuery("location.lon").lt(36.0000).gt(0.000))) 281 .scoreMode("total"); 282 283 } 284 285 /** 286 * 測試索引查詢 287 */ 288 @Test 289 public void testIndicesQueryBuilder () { 290 QueryBuilder queryBuilder = QueryBuilders.indicesQuery( 291 QueryBuilders.termQuery("user", "kimchy"), "index1", "index2") 292 .noMatchQuery(QueryBuilders.termQuery("user", "kimchy")); 293 294 } 295 296 297 298 /** 299 * 查詢遍歷抽取 300 * @param queryBuilder 301 */ 302 private void searchFunction(QueryBuilder queryBuilder) { 303 SearchResponse response = client.prepareSearch("twitter") 304 .setSearchType(SearchType.DFS_QUERY_THEN_FETCH) 305 .setScroll(new TimeValue(60000)) 306 .setQuery(queryBuilder) 307 .setSize(100).execute().actionGet(); 308 309 while(true) { 310 response = client.prepareSearchScroll(response.getScrollId()) 311 .setScroll(new TimeValue(60000)).execute().actionGet(); 312 for (SearchHit hit : response.getHits()) { 313 Iterator<Entry<String, Object>> iterator = hit.getSource().entrySet().iterator(); 314 while(iterator.hasNext()) { 315 Entry<String, Object> next = iterator.next(); 316 System.out.println(next.getKey() + ": " + next.getValue()); 317 if(response.getHits().hits().length == 0) { 318 break; 319 } 320 } 321 } 322 break; 323 } 324 // testResponse(response); 325 } 326 327 /** 328 * 對response結果的分析 329 * @param response 330 */ 331 public void testResponse(SearchResponse response) { 332 // 命中的記錄數 333 long totalHits = response.getHits().totalHits(); 334 335 for (SearchHit searchHit : response.getHits()) { 336 // 打分 337 float score = searchHit.getScore(); 338 // 文章id 339 int id = Integer.parseInt(searchHit.getSource().get("id").toString()); 340 // title 341 String title = searchHit.getSource().get("title").toString(); 342 // 內容 343 String content = searchHit.getSource().get("content").toString(); 344 // 文章更新時間 345 long updatetime = Long.parseLong(searchHit.getSource().get("updatetime").toString()); 346 } 347 } 348 349 /** 350 * 對結果設置高亮顯示 351 */ 352 public void testHighLighted() { 353 /* 5.0 版本后的高亮設置 354 * client.#().#().highlighter(hBuilder).execute().actionGet(); 355 HighlightBuilder hBuilder = new HighlightBuilder(); 356 hBuilder.preTags("<h2>"); 357 hBuilder.postTags("</h2>"); 358 hBuilder.field("user"); // 設置高亮顯示的字段 359 */ 360 // 加入查詢中 361 SearchResponse response = client.prepareSearch("blog") 362 .setQuery(QueryBuilders.matchAllQuery()) 363 .addHighlightedField("user") // 添加高亮的字段 364 .setHighlighterPreTags("<h1>") 365 .setHighlighterPostTags("</h1>") 366 .execute().actionGet(); 367 368 // 遍歷結果, 獲取高亮片段 369 SearchHits searchHits = response.getHits(); 370 for(SearchHit hit:searchHits){ 371 System.out.println("String方式打印文檔搜索內容:"); 372 System.out.println(hit.getSourceAsString()); 373 System.out.println("Map方式打印高亮內容"); 374 System.out.println(hit.getHighlightFields()); 375 376 System.out.println("遍歷高亮集合,打印高亮片段:"); 377 Text[] text = hit.getHighlightFields().get("title").getFragments(); 378 for (Text str : text) { 379 System.out.println(str.string()); 380 } 381 } 382 } 383 }
elasticsearch查詢api:match query:
match query
- 與term query不同,match query的查詢詞是被分詞處理的(analyzed),即首先分詞,然后構造相應的查詢,所以應該確保查詢的分詞器和索引的分詞器是一致的;
- 與terms query相似,提供的查詢詞之間默認是or的關系,可以通過
operator
屬性指定; - match query有兩種形式,一種是簡單形式,一種是bool形式;
match query VS match_phrase query
注意其差別:
- match query:會對查詢語句進行分詞,分詞后查詢語句中的任何一個詞項被匹配,文檔就會被搜索到。如果想查詢匹配所有關鍵詞的文檔,可以用and操作符連接;
- match_phrase query:滿足下面兩個條件才會被搜索到
- (1)分詞后所有詞項都要出現在該字段中
- (2)字段中的詞項順序要一致