第五章-簡單搜尋
眾裡尋他千百度
搜尋是ES的核心,本節講解一些基本的簡單的搜尋。
掌握ES搜尋查詢的RESTful的API猶如掌握關係型資料庫的SQL語句,儘管Java客戶端API為我們不需要我們去實際編寫RESTful的API,但在生產環境中,免不了線上上執行查詢語句做資料統計供產品經理等使用。
資料準備
首先建立一個名為user的Index,並建立一個student的Type,Mapping對映一共有如下幾個欄位:
建立名為user的Index
PUT http://localhost:9200/user
建立名為student的Type,且指定欄位name和address的分詞器為
ik_smart
。POST http://localhost:9200/user/student/_mapping { "properties":{ "name":{ "type":"text", "analyzer":"ik_smart" }, "age":{ "type":"short" } } }
經過上一章分詞的學習我們把text
型別都指定為ik_smart
分詞器。
插入以下資料。
POST localhost:9200/user/student
{
"name":"kevin",
"age":25
}
POST localhost:9200/user/student
{
"name":"kangkang",
"age":26
}
POST localhost:9200/user/student
{
"name":"mike",
"age":22
}
POST localhost:9200/user/student
{
"name":"kevin2",
"age":25
}
POST localhost:9200/user/student
{
"name":"kevin yu",
"age":21
}
按查詢條件數量維度
無條件搜尋
GET http://localhost:9200/user/student/_search?pretty
檢視索引user的student型別資料,得到剛剛插入的資料返回:
單條件搜尋
ES查詢主要分為term
精確搜尋、match
模糊搜尋。
term精確搜尋
我們用term
搜尋name為“kevin”的資料。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"term":{
"name":"kevin"
}
}
}
既然term
是精確搜尋,按照非關係型資料庫的理解來講就等同於=
,那麼搜尋結果也應該只包含1條資料。然而出乎意料的是,搜尋結果出現了兩條資料:name="kevin"和name="keivin yu",這看起來似乎是進行的模糊搜尋,但又沒有搜尋出name="kevin2"的資料。我們先繼續觀察match
的搜尋結果。
match模糊搜尋
同樣,搜尋name為“kevin”的資料。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"match":{
"name":"kevin"
}
}
}
match
的搜尋結果竟然仍然是兩條資料:name="kevin"和name="keivin yu"。同樣,name="kevin2"也沒有出現在搜尋結果中。
原因在於term
和match
的精確和模糊針對的是搜尋詞而言,term
搜尋不會將搜尋詞進行分詞後再搜尋,而match
則會將搜尋詞進行分詞後再搜尋。例如,我們對name="kevin yu"進行搜尋,由於term
搜尋不會對搜尋詞進行搜尋,所以它進行檢索的是"kevin yu"這個整體,而match
搜尋則會對搜尋詞進行分詞搜尋,所以它進行檢索的是包含"kevin"和"yu"的資料。而name欄位是text
型別,且它是按照ik_smart
進行分詞,就算是"kevin yu"這條資料由於被分詞後變成了"kevin"和"yu",所以term
搜尋不到任何結果。
如果一定要用term
搜尋name="kevin yu",結果出現"kevin yu",辦法就是在定義對映Mapping時就為該欄位設定一個keyword
型別。
為了下文的順利進行,刪除DELETE http:localhost:9200/user/student
重新按照開頭建立索引以及插入資料吧。唯一需要修改的是在定義對映Mapping時,name欄位修改為如下所示:
{
"properties":{
"name":{
"type":"text",
"analyzer":"ik_smart",
"fields":{
"keyword":{
"type":"keyword",
"ignore_abore":256
}
}
},
"age":{
"type":integer
}
}
}
待我們重新建立好索引並插入資料後,此時再按照term
搜尋name="kevin yu"。
POST http://localhost:9200/user/student/_search
{
"query":{
"term":{
"name.keyword":"kevin yu"
}
}
}
返回一條name="kevin yu"的資料。按照match
搜尋同樣出現name="kevin yu",因為name.keyword無論如何都不會再分詞。
在已經建立索引且定義好對映Mapping的情況下,如果直接修改name欄位,此時能修改成功,但是卻無法進行查詢,這與ES底層實現有關,如果一定要修改要麼是新增欄位,要麼是重建索引。
所以,與其說match
是模糊搜尋,倒不如說它是分詞搜尋,因為它會將搜尋關鍵字分詞;與其將term
稱之為模糊搜尋,倒不如稱之為不分詞搜尋,因為它不會將搜尋關鍵字分詞。
match
查詢還有很多更為高階的查詢方式:match_phrase
短語查詢,match_phrase_prefix
短語匹配查詢,multi_match
多欄位查詢等。將在複雜搜尋一章中詳細介紹。
類似like的模糊搜尋
wildcard
萬用字元查詢。
POST http://localhost:9200/user/student/_search?pretty
{
"query": {
"wildcard": {
"name": "*kevin*"
}
}
}
ES返回結果包括name="kevin",name="kevin2",name="kevin yu"。
fuzzy更智慧的模糊搜尋
fuzzy也是一個模糊查詢,它看起來更加”智慧“。它類似於搜狗輸入法中允許語法錯誤,但仍能搜出你想要的結果。例如,我們查詢name等於”kevin“的文件時,不小心輸成了”kevon“,它仍然能查詢出結構。
POST http://localhost:9200/user/student/_search?pretty
{
"query": {
"fuzzy": {
"name": "kevin"
}
}
}
ES返回結果包括name="kevin",name="kevin yu"。
多條件搜尋
上文介紹了單個條件下的簡單搜尋,並且介紹了相關的精確和模糊搜尋(分詞與不分詞)。這部分將介紹多個條件下的簡單搜尋。
當搜尋需要多個條件時,條件與條件之間的關係有”與“,”或“,“非”,正如非關係型資料庫中的”and“,”or“,“not”。
在ES中表示”與“關係的是關鍵字must
,表示”或“關係的是關鍵字should
,還有表示表示”非“的關鍵字must_not
。
must
、should
、must_not
在ES中稱為bool
查詢。當有多個查詢條件進行組合查詢時,此時需要上述關鍵字配合上文提到的term
,match
等。
- 精確查詢(
term
,搜尋關鍵字不分詞)name="kevin"且age="25"的學生。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"bool":{
"must":[{
"term":{
"name.keyword":"kevin"
}
},{
"term":{
"age":25
}
}]
}
}
}
返回name="kevin"且age="25"的資料。
- 精確查詢(
term
,搜尋關鍵字不分詞)name="kevin"或age="21"的學生。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"bool":{
"should":[{
"term":{
"name.keyword":"kevin"
}
},{
"term":{
"age":21
}
}]
}
}
}
返回name="kevin",age=25和name="kevin yu",age=21的資料
- 精確查詢(
term
,搜尋關鍵字不分詞)name!="kevin"且age="25"的學生。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"bool":{
"must":[{
"term":{
"age":25
}
}],
"must_not":[{
"term":{
"name.keyword":"kevin"
}
}]
}
}
}
返回name="kevin2"的資料。
如果查詢條件中同時包含must
、should
、must_not
,那麼它們三者是"且"的關係
多條件查詢中查詢邏輯(must
、should
、must_not
)與查詢精度(term
、match
)配合能組合成非常豐富的查詢條件。
按等值、範圍查詢維度
上文中講到了精確查詢、模糊查詢,已經"且","或","非"的查詢。基本上都是在做等值查詢,實際查詢中還包括,範圍(大於小於)查詢(range
)、存在查詢(exists
)、~不存在查詢(。missing
)
範圍查詢
範圍查詢關鍵字range
,它包括大於gt
、大於等於gte
、小於lt
、小於等於lte
。
- 查詢age>25的學生。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"range":{
"age":{
"gt":25
}
}
}
}
返回name="kangkang"的資料。
- 查詢age >= 21且age < 26的學生。
POST http://localhost:9200/user/search/_search?pretty
{
"query":{
"range":{
"age":{
"gte":21,
"lt":25
}
}
}
}
查詢age >= 21 且 age < 26且name="kevin"的學生
POST http://localhost:9200/user/search/_search?pretty
{
"query":{
"bool":{
"must":[{
"term":{
"name":"kevin"
}
},{
"range":{
"age":{
"gte":21,
"lt":25
}
}
}]
}
}
}
存在查詢
存在查詢意為查詢是否存在某個欄位。
查詢存在name欄位的資料。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"exists":{
"field":"name"
}
}
}
不存在查詢
不存在查詢顧名思義查詢不存在某個欄位的資料。在以前ES有missing
表示查詢不存在的欄位,後來的版本中由於must not
和exists
可以組合成missing
,故去掉了missing
。
查詢不存在name欄位的資料。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"bool":{
"must_not":{
"exists":{
"field":"name"
}
}
}
}
}
分頁搜尋
談到ES的分頁永遠都繞不開深分頁的問題。但在本章中暫時避開這個問題,只說明在ES中如何進行分頁查詢。
ES分頁查詢包含from
和size
關鍵字,from
表示起始值,size
表示一次查詢的數量。
- 查詢資料的總數
POST http://localhost:9200/user/student/_search?pretty
返回文件總數。
- 分頁(一頁包含1條資料)模糊查詢(
match
,搜尋關鍵字不分詞)name="kevin"
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"match":{
"name":"kevin"
}
},
"from":0,
"size":1
}
結合文件總數即可返回簡單的分頁查詢。
分頁查詢中往往我們也需要對資料進行排序返回,MySQL中使用order by
關鍵字,ES中使用sort
關鍵字指定排序欄位以及降序升序。
- 分頁(一頁包含1條資料)查詢age >= 21且age <=26的學生,按年齡降序排列。
POST http://localhost:9200/user/student/_search?pretty
{
"query":{
"range":{
"age":{
"gte":21,
"lte":26
}
}
},
"from":0,
"size":1,
"sort":{
"age":{
"order":"desc"
}
}
}
ES預設升序排列,如果不指定排序欄位的排序),則sort
欄位可直接寫為"sort":"age"
。
第六章-Java客戶端(上)
ES提供了多種方式使用Java客戶端:
- TransportClient,通過Socket方式連線ES叢集,傳輸會對Java進行序列化
- RestClient,通過HTTP方式請求ES叢集
目前常用的是TransportClient
方式連線ES服務。但ES官方表示,在未來TransportClient
會被永久移除,只保留RestClient
方式。
同樣,Spring Boot官方也提供了操作ES的方式Spring Data ElasticSearch
。本章節將首先介紹基於Spring Boot所構建的工程通過Spring Data ElasticSearch
操作ES,再介紹同樣是基於Spring Boot所構建的工程,但使用ES提供的TransportClient
操作ES。
Spring Data ElasticSearch
本節完整程式碼(配合原始碼使用更香):https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/spring-data-elasticsearch
使用Spring Data ElasticSearch
後,你會發現一切變得如此簡單。就連連線ES服務的類都不需要寫,只需要配置一條ES服務在哪兒的資訊就能開箱即用。
作為簡單的API和簡單搜尋兩章節的啟下部分,本節示例仍然是基於上一章節的示例。
通過IDEA建立Spring Boot工程,並且在建立過程中選擇Spring Data ElasticSearch
,主要步驟如下圖所示:
第一步,建立工程,選擇Spring Initializr
。
第二步,選擇SpringBoot的依賴NoSQL -> Spring Data ElasticSearch
。
建立好Spring Data ElasticSearch的Spring Boot工程後,按照ES慣例是定義Index以及Type和Mapping。在Spring Data ElasticSearch
中定義Index、Type以及Mapping非常簡單。ES文件資料實質上對應的是一個資料結構,也就是在Spring Data ElasticSearch
要我們把ES中的文件資料模型與Java物件對映關聯。
定義StudentPO物件,物件中定義Index以及Type,Mapping對映我們引入外部json檔案(json格式的Mapping就是在簡單搜尋一章中定義的Mapping資料)。
package com.coderbuff.es.easy.domain;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
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.FieldType;
import org.springframework.data.elasticsearch.annotations.Mapping;
import java.io.Serializable;
/**
* ES mapping對映對應的PO
* Created by OKevin on 2019-06-26 22:52
*/
@Getter
@Setter
@ToString
@Document(indexName = "user", type = "student")
@Mapping(mappingPath = "student_mapping.json")
public class StudentPO implements Serializable {
private String id;
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private Integer age;
}
Spring Data ElasticSearch
為我們遮蔽了操作ES太多的細節,以至於真的就是開箱即用,它操作ES主要是通過ElasticsearchRepository
介面,我們在定義自己具體業務時,只需要繼承它,擴充套件自己的方法。
package com.coderbuff.es.easy.dao;
import com.coderbuff.es.easy.domain.StudentPO;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
/**
* Created by OKevin on 2019-06-26 23:45
*/
@Repository
public interface StudentRepository extends ElasticsearchRepository<StudentPO, String> {
}
ElasticsearchTemplate
可以說是Spring Data ElasticSearch
最為重要的一個類,它對ES的Java API進行了封裝,建立索引等都離不開它。在Spring中要使用它,必然是要先注入,也就是例項化一個bean。而Spring Data ElasticSearch
早為我們做好了一切,只需要在application.properties
中定義spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
,就可大功告成(網上有人的教程還在使用applicationContext.xml定義一個bean,事實證明,受到了Spring多年的“毒害”,Spring Boot遠比我們想象的智慧)。
單元測試建立Index、Type以及定義Mapping。
package com.coderbuff.es;
import com.coderbuff.es.easy.domain.StudentPO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataElasticsearchApplicationTests {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 測試建立Index,type和Mapping定義
*/
@Test
public void createIndex() {
elasticsearchTemplate.createIndex(StudentPO.class);
elasticsearchTemplate.putMapping(StudentPO.class);
}
}
使用GET http://localhost:9200/user
請求命令,可看到通過Spring Data ElasticSearch
建立的索引。
索引建立完成後,接下來就是定義操作student文件資料的介面。在StudentService
介面的實現中,通過組合StudentRepository
類對ES進行操作。StudentRepository
類繼承了ElasticsearchRepository
介面,這個介面的實現已經為我們提供了基本的資料操作,儲存、修改、刪除只是一句程式碼的事。就算查詢、分頁也為我們提供好了builder類。"最難"的實際上不是實現這些方法,而是如何構造查詢引數SearchQuery
。建立SearchQuery
例項,有兩種方式:
- 構建
NativeSearchQueryBuilder
類,通過鏈式呼叫構造查詢引數。 - 構建
NativeSearchQuery
類,通過構造方法傳入查詢引數。
這裡以"不分頁range範圍和term查詢age>=21且age<26且name=kevin"為例。
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.rangeQuery("age").gte(21).lt(26))
.must(QueryBuilders.termQuery("name", "kevin"))).build();
搜尋條件的構造一定要對ES的查詢結構有比較清晰的認識,如果是在瞭解了簡單的API和簡單搜尋兩章的前提下,學習如何構造多加練習一定能掌握。這裡就不一一驗證前面章節的示例,一定要配合程式碼使用練習(https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/spring-data-elasticsearch)
TransportClient
ES的Java API非常廣泛,一種操作可能會有好幾種寫法。Spring Data ElasticSearch實際上是對ES Java API的再次封裝,從使用上將更加簡單。
本節請直接對照程式碼學習使用,如果要講解ES的Java API那將是一個十分龐大的工作,https://github.com/yu-linfeng/elasticsearch6.x_tutorial/tree/master/code/transportclient-elasticsearch
關注公眾號:CoderBuff,回覆“es”獲取《ElasticSearch6.x實戰教程》完整版PDF,回覆“抽獎”參與《從Lucene到Elasticsearch:全文檢索實戰》圖書抽獎活動(7.17-7.21)。