1.springboot中配置elasticSearch
1.1在工程中引入相關的jar包
1.1.1 在build.gradle中添加需要的jar包
我建立的gradle工程,對應的maven工程也是一樣,添加對應的jar包即可
-
// 添加 Spring Data Elasticsearch 的依賴
-
compile('org.springframework.boot:spring-boot-starter-data-elasticsearch')
-
// 添加 JNA 的依賴,java通路目前作業系統需要的包
-
compile('net.java.dev.jna:jna:4.3.0')
1.1.2在application.properties添加elasticsearch的配置
-
#es的預設名稱,如果安裝es時沒有做特殊的操作名字都是此名稱
-
spring.data.elasticsearch.cluster-name=elasticsearch
-
# Elasticsearch 叢集節點服務位址,用逗号分隔,如果沒有指定其他就啟動一個用戶端節點,預設java通路端口9300
-
spring.data.elasticsearch.cluster-nodes=localhost:9300
-
# 設定連接配接逾時時間
-
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
1.2建立文檔實體對象
-
package site.wlss.blog.domain.es;
-
import java.io.Serializable;
-
import java.sql.Timestamp;
-
import org.springframework.data.annotation.Id;
-
import org.springframework.data.elasticsearch.annotations.Document;
-
import org.springframework.data.elasticsearch.annotations.Field;
-
import org.springframework.data.elasticsearch.annotations.FieldIndex;
-
import site.wlss.blog.domain.Blog;
-
@Document(indexName = "blog", type = "blog")
-
public class EsBlog implements Serializable {
-
private static final long serialVersionUID = 1L;
-
@Id // 主鍵,注意這個搜尋是id類型是string,與我們常用的不同
-
private String id; //@Id注解加上後,在Elasticsearch裡相應于該列就是主鍵了,在查詢時就可以直接用主鍵查詢
-
@Field(index = FieldIndex.not_analyzed) // 不做全文檢索字段
-
private Long blogId; // Blog 實體的 id,這兒增加了一個blog的id屬性
-
private String title;
-
private String summary;
-
private String content;
-
@Field(index = FieldIndex.not_analyzed) // 不做全文檢索字段
上面是我的部分代碼,注意要對實體對象有個@Document注解,對象的id也有個@id的注解,其中還有個@Field的注解,這是對該字段的說明,下面對這些注解給出詳細解釋
解釋一:@Document注解
@Document注解裡面的幾個屬性,類比mysql的話是這樣:
indexName –> 索引庫的名稱,建議以項目的名稱命名,就相當于資料庫DB
type –> 類型,建議以實體的名稱命名Table ,就相當于資料庫中的表table
Document –> row 就相當于某一個具體對象
附上注解的内容:
-
String indexName();//索引庫的名稱,建議以項目的名稱命名
-
String type() default "";//類型,建議以實體的名稱命名
-
short shards() default 5;//預設分區數
-
short replicas() default 1;//每個分區預設的備份數
-
String refreshInterval() default "1s";//重新整理間隔
-
String indexStoreType() default "fs";//索引檔案存儲類型
解釋二:@Id注解
在Elasticsearch裡相應于該列就是主鍵了,在查詢時就可以直接用主鍵查詢
解釋三:@Field注解
-
public @interface Field {
-
FieldType type() default FieldType.Auto;#自動檢測屬性的類型
-
FieldIndex index() default FieldIndex.analyzed;#預設情況下分詞
-
DateFormat format() default DateFormat.none;
-
String pattern() default "";
-
boolean store() default false;#預設情況下不存儲原文
-
String searchAnalyzer() default "";#指定字段搜尋時使用的分詞器
-
String indexAnalyzer() default "";#指定字段建立索引時指定的分詞器
-
String[] ignoreFields() default {};#如果某個字段需要被忽略
-
boolean includeInParent() default false;
-
}
2.通過jpa建立文檔庫
因為我們引入的是spring data的elasticsearch是以它遵循spring data的接口,也就是說操作elasticSearch與操作spring data jpa的方法是完全一樣的,我們隻将文檔庫繼承ElasticsearchRepository即可。
-
package site.wlss.blog.repository.es;
-
import org.springframework.data.domain.Page;
-
import org.springframework.data.domain.Pageable;
-
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
-
import site.wlss.blog.domain.es.EsBlog;
-
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog, String> {
-
//下面是我們根據 spring data jpa 的命名規範額外建立的兩個查詢方法
-
Page<EsBlog> findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContainingOrTagsContaining(String title,String Summary,String content,String tags,Pageable pageable);
-
EsBlog findByBlogId(Long blogId);
-
}
裡面的内容是我根據spring data jpa 額外建立的兩個方法。
3.根據reporitory查詢文檔
這個方法和操作jpa中的普通的方法沒什麼差別,就是普通的增删改查。
4.ElasticSearch的進階複雜查詢:非聚合查詢和聚合查詢
這兒才是我今天要講的重點。
4.1非聚合複雜查詢(這兒展示了非聚合複雜查詢的常用流程)
-
public List<EsBlog> elasticSerchTest() {
-
//1.建立QueryBuilder(即設定查詢條件)這兒建立的是組合查詢(也叫多條件查詢),後面會介紹更多的查詢方法
-
BoolQueryBuilder builder = QueryBuilders.boolQuery();
-
//builder下有must、should以及mustNot 相當于sql中的and、or以及not
-
//設定模糊搜尋,部落格的簡訴中有學習兩個字
-
builder.must(QueryBuilders.fuzzyQuery("sumary", "學習"));
-
//設定要查詢部落格的标題中含有關鍵字
-
builder.must(new QueryStringQueryBuilder("man").field("springdemo"));
-
//按照部落格的評論數的排序是依次降低
-
FieldSortBuilder sort = SortBuilders.fieldSort("commentSize").order(SortOrder.DESC);
-
//設定分頁(從第一頁開始,一頁顯示10條)
-
//注意開始是從0開始,有點類似sql中的方法limit 的查詢
-
PageRequest page = new PageRequest(0, 10);
-
//2.建構查詢
-
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
-
//将搜尋條件設定到建構中
-
nativeSearchQueryBuilder.withQuery(builder);
-
//将分頁設定到建構中
-
nativeSearchQueryBuilder.withPageable(page);
-
//将排序設定到建構中
-
nativeSearchQueryBuilder.withSort(sort);
-
//生産NativeSearchQuery
-
NativeSearchQuery query = nativeSearchQueryBuilder.build();
-
//3.執行方法1
-
Page<EsBlog> page = esBlogRepository.search(query);
-
//執行方法2:注意,這兒執行的時候還有個方法那就是使用elasticsearchTemplate
-
//執行方法2的時候需要加上注解
-
//@Autowired
-
//private ElasticsearchTemplate elasticsearchTemplate;
-
List<EsBlog> blogList = elasticsearchTemplate.queryForList(query, EsBlog.class);
-
//4.擷取總條數(用于前端分頁)
-
int total = (int) page.getTotalElements();
-
//5.擷取查詢到的資料内容(傳回給前端)
-
List<EsBlog> content = page.getContent();
-
return content;
-
}
4.2查詢條件QueryBuilder的建構方法舉例
在使用聚合查詢之前我們有必要先來了解下建立查詢條件QueryBuilder的幾種常用方法
4.2.1精确查詢(必須完全比對上)
單個比對termQuery
-
//不分詞查詢 參數1: 字段名,參數2:字段查詢值,因為不分詞,是以漢字隻能查詢一個字,英語是一個單詞.
-
QueryBuilder queryBuilder=QueryBuilders.termQuery("fieldName", "fieldlValue");
-
//分詞查詢,采用預設的分詞器
-
QueryBuilder queryBuilder2 = QueryBuilders.matchQuery("fieldName", "fieldlValue");
多個比對
-
//不分詞查詢,參數1: 字段名,參數2:多個字段查詢值,因為不分詞,是以漢字隻能查詢一個字,英語是一個單詞.
-
QueryBuilder queryBuilder=QueryBuilders.termsQuery("fieldName", "fieldlValue1","fieldlValue2...");
-
//分詞查詢,采用預設的分詞器
-
QueryBuilder queryBuilder= QueryBuilders.multiMatchQuery("fieldlValue", "fieldName1", "fieldName2", "fieldName3");
-
//比對所有檔案,相當于就沒有設定查詢條件
-
QueryBuilder queryBuilder=QueryBuilders.matchAllQuery();
4.2.2模糊查詢(隻要包含即可)
-
//模糊查詢常見的5個方法如下
-
//1.常用的字元串查詢
-
QueryBuilders.queryStringQuery("fieldValue").field("fieldName");//左右模糊
-
//2.常用的用于推薦相似内容的查詢
-
QueryBuilders.moreLikeThisQuery(new String[] {"fieldName"}).addLikeText("pipeidhua");//如果不指定filedName,則預設全部,常用在相似内容的推薦上
-
//3.字首查詢 如果字段沒分詞,就比對整個字段字首
-
QueryBuilders.prefixQuery("fieldName","fieldValue");
-
//4.fuzzy query:分詞模糊查詢,通過增加fuzziness模糊屬性來查詢,如能夠比對hotelName為tel前或後加一個字母的文檔,fuzziness 的含義是檢索的term 前後增加或減少n個單詞的比對查詢
-
QueryBuilders.fuzzyQuery("hotelName", "tel").fuzziness(Fuzziness.ONE);
-
//5.wildcard query:通配符查詢,支援* 任意字元串;?任意一個字元
-
QueryBuilders.wildcardQuery("fieldName","ctr*");//前面是fieldname,後面是帶比對字元的字元串
-
QueryBuilders.wildcardQuery("fieldName","c?r?");
4.2.3範圍查詢
-
//閉區間查詢
-
QueryBuilder queryBuilder0 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2");
-
//開區間查詢
-
QueryBuilder queryBuilder1 = QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2").includeUpper(false).includeLower(false);//預設是true,也就是包含
-
//大于
-
QueryBuilder queryBuilder2 = QueryBuilders.rangeQuery("fieldName").gt("fieldValue");
-
//大于等于
-
QueryBuilder queryBuilder3 = QueryBuilders.rangeQuery("fieldName").gte("fieldValue");
-
//小于
-
QueryBuilder queryBuilder4 = QueryBuilders.rangeQuery("fieldName").lt("fieldValue");
-
//小于等于
-
QueryBuilder queryBuilder5 = QueryBuilders.rangeQuery("fieldName").lte("fieldValue");
4.2.4組合查詢/多條件查詢/布爾查詢
-
QueryBuilders.boolQuery()
-
QueryBuilders.boolQuery().must();//文檔必須完全比對條件,相當于and
-
QueryBuilders.boolQuery().mustNot();//文檔必須不比對條件,相當于not
-
QueryBuilders.boolQuery().should();//至少滿足一個條件,這個文檔就符合should,相當于or
4.3聚合查詢
Elasticsearch有一個功能叫做 聚合(aggregations) ,它允許你在資料上生成複雜的分析統計。它很像SQL中的 GROUP BY 但是功能更強大。
為了掌握聚合,你隻需要了解兩個主要概念:(參考https://blog.csdn.net/dm_vincent/article/details/42387161)
Buckets(桶):滿足某個條件的文檔集合。
Metrics(名額):為某個桶中的文檔計算得到的統計資訊。
就是這樣!每個聚合隻是簡單地由一個或者多個桶,零個或者多個名額組合而成。可以将它粗略地轉換為SQL:
-
SELECT COUNT(color)
-
FROM table
-
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的查詢延遲。
将兩者結合起來
一個聚合就是一些桶和名額的組合。一個聚合可以隻有一個桶,或者一個名額,或者每樣一個。在桶中甚至可以有多個嵌套的桶。比如,我們可以将文檔按照其所屬國家進行分桶,然後對每個桶計算其平均薪資(一個名額)。
因為桶是可以嵌套的,我們能夠實作一個更加複雜的聚合操作:
- 将文檔按照國家進行分桶。(桶)
- 然後将每個國家的桶再按照性别分桶。(桶)
- 然後将每個性别的桶按照年齡區間進行分桶。(桶)
- 最後,為每個年齡區間計算平均薪資。(名額)
聚合查詢都是由AggregationBuilders建立的,一些常見的聚合查詢如下
(參考:http://blog.csdn.net/u010454030/article/details/63266035)
-
(1)統計某個字段的數量
-
ValueCountBuilder vcb= AggregationBuilders.count("count_uid").field("uid");
-
(2)去重統計某個字段的數量(有少量誤差)
-
CardinalityBuilder cb= AggregationBuilders.cardinality("distinct_count_uid").field("uid");
-
(3)聚合過濾
-
FilterAggregationBuilder fab= AggregationBuilders.filter("uid_filter").filter(QueryBuilders.queryStringQuery("uid:001"));
-
(4)按某個字段分組
-
TermsBuilder tb= AggregationBuilders.terms("group_name").field("name");
-
(5)求和
-
SumBuilder sumBuilder= AggregationBuilders.sum("sum_price").field("price");
-
(6)求平均
-
AvgBuilder ab= AggregationBuilders.avg("avg_price").field("price");
-
(7)求最大值
-
MaxBuilder mb= AggregationBuilders.max("max_price").field("price");
-
(8)求最小值
-
MinBuilder min= AggregationBuilders.min("min_price").field("price");
-
(9)按日期間隔分組
-
DateHistogramBuilder dhb= AggregationBuilders.dateHistogram("dh").field("date");
-
(10)擷取聚合裡面的結果
-
TopHitsBuilder thb= AggregationBuilders.topHits("top_result");
-
(11)嵌套的聚合
-
NestedBuilder nb= AggregationBuilders.nested("negsted_path").path("quests");
-
(12)反轉嵌套
-
AggregationBuilders.reverseNested("res_negsted").path("kps ");
聚合查詢的詳細使用步驟如下:
-
public void test(){
-
//目标:搜尋寫部落格寫得最多的使用者(一個部落格對應一個使用者),通過搜尋部落格中的使用者名的頻次來達到想要的結果
-
//首先建立一個用于存儲資料的集合
-
List<String> ueserNameList=new ArrayList<>();
-
//1.建立查詢條件,也就是QueryBuild
-
QueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();//設定查詢所有,相當于不設定查詢條件
-
//2.建構查詢
-
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
-
//2.0 設定QueryBuilder
-
nativeSearchQueryBuilder.withQuery(matchAllQuery);
-
//2.1設定搜尋類型,預設值就是QUERY_THEN_FETCH,參考https://blog.csdn.net/wulex/article/details/71081042
-
nativeSearchQueryBuilder.withSearchType(SearchType.QUERY_THEN_FETCH);//指定索引的類型,隻先從各分片中查詢比對的文檔,再重新排序和排名,取前size個文檔
-
//2.2指定索引庫和文檔類型
-
nativeSearchQueryBuilder.withIndices("myBlog").withTypes("blog");//指定要查詢的索引庫的名稱和類型,其實就是我們文檔@Document中設定的indedName和type
-
//2.3重點來了!!!指定聚合函數,本例中以某個字段分組聚合為例(可根據你自己的聚合查詢需求設定)
-
//該聚合函數解釋:計算該字段(假設為username)在所有文檔中的出現頻次,并按照降序排名(常用于某個字段的熱度排名)
-
TermsBuilder termsAggregation = AggregationBuilders.terms("給聚合查詢取的名").field("username").order(Terms.Order.count(false));
-
nativeSearchQueryBuilder.addAggregation(termsAggregation);
-
//2.4建構查詢對象
-
NativeSearchQuery nativeSearchQuery = nativeSearchQueryBuilder.build();
-
//3.執行查詢
-
//3.1方法1,通過reporitory執行查詢,獲得有Page包裝了的結果集
-
Page<EsBlog> search = esBlogRepository.search(nativeSearchQuery);
-
List<EsBlog> content = search.getContent();
-
for (EsBlog esBlog : content) {
-
ueserNameList.add(esBlog.getUsername());
-
}
-
//獲得對應的文檔之後我就可以獲得該文檔的作者,那麼就可以查出最熱門使用者了
-
//3.2方法2,通過elasticSearch模闆elasticsearchTemplate.queryForList方法查詢
-
List<EsBlog> queryForList = elasticsearchTemplate.queryForList(nativeSearchQuery, EsBlog.class);
-
//3.3方法3,通過elasticSearch模闆elasticsearchTemplate.query()方法查詢,獲得聚合(常用)
-
Aggregations aggregations = elasticsearchTemplate.query(nativeSearchQuery, new ResultsExtractor<Aggregations>() {
-
@Override
-
public Aggregations extract(SearchResponse response) {
-
return response.getAggregations();
-
}
-
});
-
//轉換成map集合
-
Map<String, Aggregation> aggregationMap = aggregations.asMap();
-
//獲得對應的聚合函數的聚合子類,該聚合子類也是個map集合,裡面的value就是桶Bucket,我們要獲得Bucket
-
StringTerms stringTerms = (StringTerms) aggregationMap.get("給聚合查詢取的名");
-
//獲得所有的桶
-
List<Bucket> buckets = stringTerms.getBuckets();
-
//将集合轉換成疊代器周遊桶,當然如果你不删除buckets中的元素,直接foreach周遊就可以了
-
Iterator<Bucket> iterator = buckets.iterator();
-
while(iterator.hasNext()) {
-
//bucket桶也是一個map對象,我們取它的key值就可以了
-
String username = iterator.next().getKeyAsString();//或者bucket.getKey().toString();
-
//根據username去結果中查詢即可對應的文檔,添加存儲資料的集合
-
ueserNameList.add(username);
-
}
-
//最後根據ueserNameList搜尋對應的結果集
-
List<User> listUsersByUsernames = userService.listUsersByUsernames(ueserNameList);
-
}