天天看點

springboot中ElasticSearch入門與進階:組合查詢、Aggregation聚合查詢1.springboot中配置elasticSearch2.通過jpa建立文檔庫3.根據reporitory查詢文檔4.ElasticSearch的進階複雜查詢:非聚合查詢和聚合查詢

1.springboot中配置elasticSearch

1.1在工程中引入相關的jar包 

 1.1.1 在build.gradle中添加需要的jar包

      我建立的gradle工程,對應的maven工程也是一樣,添加對應的jar包即可

  1. // 添加 Spring Data Elasticsearch 的依賴

  2. compile('org.springframework.boot:spring-boot-starter-data-elasticsearch')

  3. // 添加 JNA 的依賴,java通路目前作業系統需要的包

  4. compile('net.java.dev.jna:jna:4.3.0')

1.1.2在application.properties添加elasticsearch的配置

  1. #es的預設名稱,如果安裝es時沒有做特殊的操作名字都是此名稱

  2. spring.data.elasticsearch.cluster-name=elasticsearch

  3. # Elasticsearch 叢集節點服務位址,用逗号分隔,如果沒有指定其他就啟動一個用戶端節點,預設java通路端口9300

  4. spring.data.elasticsearch.cluster-nodes=localhost:9300

  5. # 設定連接配接逾時時間

  6. spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s

1.2建立文檔實體對象

  1. package site.wlss.blog.domain.es;

  2. import java.io.Serializable;

  3. import java.sql.Timestamp;

  4. import org.springframework.data.annotation.Id;

  5. import org.springframework.data.elasticsearch.annotations.Document;

  6. import org.springframework.data.elasticsearch.annotations.Field;

  7. import org.springframework.data.elasticsearch.annotations.FieldIndex;

  8. import site.wlss.blog.domain.Blog;

  9. @Document(indexName = "blog", type = "blog")

  10. public class EsBlog implements Serializable {

  11. private static final long serialVersionUID = 1L;

  12. @Id // 主鍵,注意這個搜尋是id類型是string,與我們常用的不同

  13. private String id; //@Id注解加上後,在Elasticsearch裡相應于該列就是主鍵了,在查詢時就可以直接用主鍵查詢

  14. @Field(index = FieldIndex.not_analyzed) // 不做全文檢索字段

  15. private Long blogId; // Blog 實體的 id,這兒增加了一個blog的id屬性

  16. private String title;

  17. private String summary;

  18. private String content;

  19. @Field(index = FieldIndex.not_analyzed) // 不做全文檢索字段

上面是我的部分代碼,注意要對實體對象有個@Document注解,對象的id也有個@id的注解,其中還有個@Field的注解,這是對該字段的說明,下面對這些注解給出詳細解釋

解釋一:@Document注解

@Document注解裡面的幾個屬性,類比mysql的話是這樣: 

indexName –> 索引庫的名稱,建議以項目的名稱命名,就相當于資料庫DB

type –> 類型,建議以實體的名稱命名Table ,就相當于資料庫中的表table

Document –> row 就相當于某一個具體對象

附上注解的内容:

  1. String indexName();//索引庫的名稱,建議以項目的名稱命名

  2. String type() default "";//類型,建議以實體的名稱命名

  3. short shards() default 5;//預設分區數

  4. short replicas() default 1;//每個分區預設的備份數

  5. String refreshInterval() default "1s";//重新整理間隔

  6. String indexStoreType() default "fs";//索引檔案存儲類型

解釋二:@Id注解

在Elasticsearch裡相應于該列就是主鍵了,在查詢時就可以直接用主鍵查詢

解釋三:@Field注解

  1. public @interface Field {

  2. FieldType type() default FieldType.Auto;#自動檢測屬性的類型

  3. FieldIndex index() default FieldIndex.analyzed;#預設情況下分詞

  4. DateFormat format() default DateFormat.none;

  5. String pattern() default "";

  6. boolean store() default false;#預設情況下不存儲原文

  7. String searchAnalyzer() default "";#指定字段搜尋時使用的分詞器

  8. String indexAnalyzer() default "";#指定字段建立索引時指定的分詞器

  9. String[] ignoreFields() default {};#如果某個字段需要被忽略

  10. boolean includeInParent() default false;

  11. }

2.通過jpa建立文檔庫

因為我們引入的是spring data的elasticsearch是以它遵循spring data的接口,也就是說操作elasticSearch與操作spring data jpa的方法是完全一樣的,我們隻将文檔庫繼承ElasticsearchRepository即可。

  1. package site.wlss.blog.repository.es;

  2. import org.springframework.data.domain.Page;

  3. import org.springframework.data.domain.Pageable;

  4. import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

  5. import site.wlss.blog.domain.es.EsBlog;

  6. public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, String> {

  7. //下面是我們根據 spring data jpa 的命名規範額外建立的兩個查詢方法

  8. Page<EsBlog> findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContainingOrTagsContaining(String title,String Summary,String content,String tags,Pageable pageable);

  9. EsBlog findByBlogId(Long blogId);

  10. }

裡面的内容是我根據spring data jpa 額外建立的兩個方法。

3.根據reporitory查詢文檔

這個方法和操作jpa中的普通的方法沒什麼差別,就是普通的增删改查。

4.ElasticSearch的進階複雜查詢:非聚合查詢和聚合查詢

這兒才是我今天要講的重點。

4.1非聚合複雜查詢(這兒展示了非聚合複雜查詢的常用流程)

  1. public List<EsBlog> elasticSerchTest() {

  2. //1.建立QueryBuilder(即設定查詢條件)這兒建立的是組合查詢(也叫多條件查詢),後面會介紹更多的查詢方法

  3. BoolQueryBuilder builder = QueryBuilders.boolQuery();

  4. //builder下有must、should以及mustNot 相當于sql中的and、or以及not

  5. //設定模糊搜尋,部落格的簡訴中有學習兩個字

  6. builder.must(QueryBuilders.fuzzyQuery("sumary", "學習"));

  7. //設定要查詢部落格的标題中含有關鍵字

  8. builder.must(new QueryStringQueryBuilder("man").field("springdemo"));

  9. //按照部落格的評論數的排序是依次降低

  10. FieldSortBuilder sort = SortBuilders.fieldSort("commentSize").order(SortOrder.DESC);

  11. //設定分頁(從第一頁開始,一頁顯示10條)

  12. //注意開始是從0開始,有點類似sql中的方法limit 的查詢

  13. PageRequest page = new PageRequest(0, 10);

  14. //2.建構查詢

  15. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

  16. //将搜尋條件設定到建構中

  17. nativeSearchQueryBuilder.withQuery(builder);

  18. //将分頁設定到建構中

  19. nativeSearchQueryBuilder.withPageable(page);

  20. //将排序設定到建構中

  21. nativeSearchQueryBuilder.withSort(sort);

  22. //生産NativeSearchQuery

  23. NativeSearchQuery query = nativeSearchQueryBuilder.build();

  24. //3.執行方法1

  25. Page<EsBlog> page = esBlogRepository.search(query);

  26. //執行方法2:注意,這兒執行的時候還有個方法那就是使用elasticsearchTemplate

  27. //執行方法2的時候需要加上注解

  28. //@Autowired

  29. //private ElasticsearchTemplate elasticsearchTemplate;

  30. List<EsBlog> blogList = elasticsearchTemplate.queryForList(query, EsBlog.class);

  31. //4.擷取總條數(用于前端分頁)

  32. int total = (int) page.getTotalElements();

  33. //5.擷取查詢到的資料内容(傳回給前端)

  34. List<EsBlog> content = page.getContent();

  35. return content;

  36. }

4.2查詢條件QueryBuilder的建構方法舉例

在使用聚合查詢之前我們有必要先來了解下建立查詢條件QueryBuilder的幾種常用方法

4.2.1精确查詢(必須完全比對上)

單個比對termQuery

  1. //不分詞查詢 參數1: 字段名,參數2:字段查詢值,因為不分詞,是以漢字隻能查詢一個字,英語是一個單詞.

  2. QueryBuilder queryBuilder=QueryBuilders.termQuery("fieldName", "fieldlValue");

  3. //分詞查詢,采用預設的分詞器

  4. QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("fieldName", "fieldlValue");

多個比對

  1. //不分詞查詢,參數1: 字段名,參數2:多個字段查詢值,因為不分詞,是以漢字隻能查詢一個字,英語是一個單詞.

  2. QueryBuilder queryBuilder=QueryBuilders.termsQuery("fieldName", "fieldlValue1","fieldlValue2...");

  3. //分詞查詢,采用預設的分詞器

  4. QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("fieldlValue", "fieldName1", "fieldName2", "fieldName3");

  5. //比對所有檔案,相當于就沒有設定查詢條件

  6. QueryBuilder queryBuilder=QueryBuilders.matchAllQuery();

4.2.2模糊查詢(隻要包含即可)

  1. //模糊查詢常見的5個方法如下

  2. //1.常用的字元串查詢

  3. QueryBuilders.queryStringQuery("fieldValue").field("fieldName");//左右模糊

  4. //2.常用的用于推薦相似内容的查詢

  5. QueryBuilders.moreLikeThisQuery(new String[] {"fieldName"}).addLikeText("pipeidhua");//如果不指定filedName,則預設全部,常用在相似内容的推薦上

  6. //3.字首查詢 如果字段沒分詞,就比對整個字段字首

  7. QueryBuilders.prefixQuery("fieldName","fieldValue");

  8. //4.fuzzy query:分詞模糊查詢,通過增加fuzziness模糊屬性來查詢,如能夠比對hotelName為tel前或後加一個字母的文檔,fuzziness 的含義是檢索的term 前後增加或減少n個單詞的比對查詢

  9. QueryBuilders.fuzzyQuery("hotelName", "tel").fuzziness(Fuzziness.ONE);

  10. //5.wildcard query:通配符查詢,支援* 任意字元串;?任意一個字元

  11. QueryBuilders.wildcardQuery("fieldName","ctr*");//前面是fieldname,後面是帶比對字元的字元串

  12. QueryBuilders.wildcardQuery("fieldName","c?r?");

4.2.3範圍查詢

  1. //閉區間查詢

  2. QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2");

  3. //開區間查詢

  4. QueryBuilder queryBuilder1 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2").includeUpper(false).includeLower(false);//預設是true,也就是包含

  5. //大于

  6. QueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("fieldName").gt("fieldValue");

  7. //大于等于

  8. QueryBuilder queryBuilder3 = QueryBuilders.rangeQuery("fieldName").gte("fieldValue");

  9. //小于

  10. QueryBuilder queryBuilder4 = QueryBuilders.rangeQuery("fieldName").lt("fieldValue");

  11. //小于等于

  12. QueryBuilder queryBuilder5 = QueryBuilders.rangeQuery("fieldName").lte("fieldValue");

4.2.4組合查詢/多條件查詢/布爾查詢

  1. QueryBuilders.boolQuery()

  2. QueryBuilders.boolQuery().must();//文檔必須完全比對條件,相當于and

  3. QueryBuilders.boolQuery().mustNot();//文檔必須不比對條件,相當于not

  4. QueryBuilders.boolQuery().should();//至少滿足一個條件,這個文檔就符合should,相當于or

4.3聚合查詢

Elasticsearch有一個功能叫做 聚合(aggregations) ,它允許你在資料上生成複雜的分析統計。它很像SQL中的 GROUP BY 但是功能更強大。

為了掌握聚合,你隻需要了解兩個主要概念:(參考https://blog.csdn.net/dm_vincent/article/details/42387161)

Buckets(桶):滿足某個條件的文檔集合。

Metrics(名額):為某個桶中的文檔計算得到的統計資訊。

就是這樣!每個聚合隻是簡單地由一個或者多個桶,零個或者多個名額組合而成。可以将它粗略地轉換為SQL:

  1. SELECT COUNT(color)

  2. FROM table

  3. GROUP BY color

以上的COUNT(color)就相當于一個名額。GROUP BY color則相當于一個桶。

桶和SQL中的組(Grouping)擁有相似的概念,而名額則與COUNT(),SUM(),MAX()等相似。

讓我們仔細看看這些概念。

桶(Buckets)

一個桶就是滿足特定條件的一個文檔集合:

  • 一名員工要麼屬于男性桶,或者女性桶。
  • 城市Albany屬于New York州這個桶。
  • 日期2014-10-28屬于十月份這個桶。

随着聚合被執行,每份文檔中的值會被計算來決定它們是否比對了桶的條件。如果比對成功,那麼該文檔會被置入該桶中,同時聚合會繼續執行。

桶也能夠嵌套在其它桶中,能讓你完成層次或者條件劃分這些需求。比如,Cincinnati可以被放置在Ohio州這個桶中,而整個Ohio州則能夠被放置在美國這個桶中。

ES中有很多類型的桶,讓你可以将文檔通過多種方式進行劃分(按小時,按最流行的詞條,按年齡區間,按地理位置,以及更多)。但是從根本上,它們都根據相同的原理運作:按照條件對文檔進行劃分。

名額(Metrics)

桶能夠讓我們對文檔進行有意義的劃分,但是最終我們還是需要對每個桶中的文檔進行某種名額計算。分桶是達到最終目的的手段:提供了對文檔進行劃分的方法,進而讓你能夠計算需要的名額。

多數名額僅僅是簡單的數學運算(比如,min,mean,max以及sum),它們使用文檔中的值進行計算。在實際應用中,名額能夠讓你計算例如平均薪資,最高出售價格,或者百分之95的查詢延遲。

将兩者結合起來

一個聚合就是一些桶和名額的組合。一個聚合可以隻有一個桶,或者一個名額,或者每樣一個。在桶中甚至可以有多個嵌套的桶。比如,我們可以将文檔按照其所屬國家進行分桶,然後對每個桶計算其平均薪資(一個名額)。

因為桶是可以嵌套的,我們能夠實作一個更加複雜的聚合操作:

  1. 将文檔按照國家進行分桶。(桶)
  2. 然後将每個國家的桶再按照性别分桶。(桶)
  3. 然後将每個性别的桶按照年齡區間進行分桶。(桶)
  4. 最後,為每個年齡區間計算平均薪資。(名額)

聚合查詢都是由AggregationBuilders建立的,一些常見的聚合查詢如下

(參考:http://blog.csdn.net/u010454030/article/details/63266035)

  1. (1)統計某個字段的數量

  2. ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");

  3. (2)去重統計某個字段的數量(有少量誤差)

  4. CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");

  5. (3)聚合過濾

  6. FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));

  7. (4)按某個字段分組

  8. TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");

  9. (5)求和

  10. SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");

  11. (6)求平均

  12. AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");

  13. (7)求最大值

  14. MaxBuilder mb= AggregationBuilders.max("max_price").field("price");

  15. (8)求最小值

  16. MinBuilder min= AggregationBuilders.min("min_price").field("price");

  17. (9)按日期間隔分組

  18. DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");

  19. (10)擷取聚合裡面的結果

  20. TopHitsBuilder thb= AggregationBuilders.topHits("top_result");

  21. (11)嵌套的聚合

  22. NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");

  23. (12)反轉嵌套

  24. AggregationBuilders.reverseNested("res_negsted").path("kps ");

聚合查詢的詳細使用步驟如下:

  1. public void test(){

  2. //目标:搜尋寫部落格寫得最多的使用者(一個部落格對應一個使用者),通過搜尋部落格中的使用者名的頻次來達到想要的結果

  3. //首先建立一個用于存儲資料的集合

  4. List<String> ueserNameList=new ArrayList<>();

  5. //1.建立查詢條件,也就是QueryBuild

  6. QueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();//設定查詢所有,相當于不設定查詢條件

  7. //2.建構查詢

  8. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

  9. //2.0 設定QueryBuilder

  10. nativeSearchQueryBuilder.withQuery(matchAllQuery);

  11. //2.1設定搜尋類型,預設值就是QUERY_THEN_FETCH,參考https://blog.csdn.net/wulex/article/details/71081042

  12. nativeSearchQueryBuilder.withSearchType(SearchType.QUERY_THEN_FETCH);//指定索引的類型,隻先從各分片中查詢比對的文檔,再重新排序和排名,取前size個文檔

  13. //2.2指定索引庫和文檔類型

  14. nativeSearchQueryBuilder.withIndices("myBlog").withTypes("blog");//指定要查詢的索引庫的名稱和類型,其實就是我們文檔@Document中設定的indedName和type

  15. //2.3重點來了!!!指定聚合函數,本例中以某個字段分組聚合為例(可根據你自己的聚合查詢需求設定)

  16. //該聚合函數解釋:計算該字段(假設為username)在所有文檔中的出現頻次,并按照降序排名(常用于某個字段的熱度排名)

  17. TermsBuilder termsAggregation = AggregationBuilders.terms("給聚合查詢取的名").field("username").order(Terms.Order.count(false));

  18. nativeSearchQueryBuilder.addAggregation(termsAggregation);

  19. //2.4建構查詢對象

  20. NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();

  21. //3.執行查詢

  22. //3.1方法1,通過reporitory執行查詢,獲得有Page包裝了的結果集

  23. Page<EsBlog> search = esBlogRepository.search(nativeSearchQuery);

  24. List<EsBlog> content = search.getContent();

  25. for (EsBlog esBlog : content) {

  26. ueserNameList.add(esBlog.getUsername());

  27. }

  28. //獲得對應的文檔之後我就可以獲得該文檔的作者,那麼就可以查出最熱門使用者了

  29. //3.2方法2,通過elasticSearch模闆elasticsearchTemplate.queryForList方法查詢

  30. List<EsBlog> queryForList = elasticsearchTemplate.queryForList(nativeSearchQuery, EsBlog.class);

  31. //3.3方法3,通過elasticSearch模闆elasticsearchTemplate.query()方法查詢,獲得聚合(常用)

  32. Aggregations aggregations = elasticsearchTemplate.query(nativeSearchQuery, new ResultsExtractor<Aggregations>() {

  33. @Override

  34. public Aggregations extract(SearchResponse response) {

  35. return response.getAggregations();

  36. }

  37. });

  38. //轉換成map集合

  39. Map<String, Aggregation> aggregationMap = aggregations.asMap();

  40. //獲得對應的聚合函數的聚合子類,該聚合子類也是個map集合,裡面的value就是桶Bucket,我們要獲得Bucket

  41. StringTerms stringTerms = (StringTerms) aggregationMap.get("給聚合查詢取的名");

  42. //獲得所有的桶

  43. List<Bucket> buckets = stringTerms.getBuckets();

  44. //将集合轉換成疊代器周遊桶,當然如果你不删除buckets中的元素,直接foreach周遊就可以了

  45. Iterator<Bucket> iterator = buckets.iterator();

  46. while(iterator.hasNext()) {

  47. //bucket桶也是一個map對象,我們取它的key值就可以了

  48. String username = iterator.next().getKeyAsString();//或者bucket.getKey().toString();

  49. //根據username去結果中查詢即可對應的文檔,添加存儲資料的集合

  50. ueserNameList.add(username);

  51. }

  52. //最後根據ueserNameList搜尋對應的結果集

  53. List<User> listUsersByUsernames = userService.listUsersByUsernames(ueserNameList);

  54. }

繼續閱讀