目錄
1.初識elasticsearch
1.1.了解ES
1.1.1.elasticsearch的作用
1.1.2.ELK技術(shù)棧
1.1.3.elasticsearch和lucene
1.1.4.為什么不是其他搜索技術(shù)?
1.1.5.總結(jié)
1.2.倒排索引
1.2.1.正向索引
1.2.2.倒排索引
1.2.3.正向和倒排
1.3.es的一些概念
1.3.1.文檔和字段
1.3.2.索引和映射
1.3.3.mysql與elasticsearch
1.4.安裝es、kibana
1.4.1.總結(jié)
2.索引庫操作
2.1.mapping映射屬性
2.2.索引庫的CRUD
2.2.1.創(chuàng)建索引庫和映射
2.2.2.查詢索引庫
2.2.3.修改索引庫
2.2.4.刪除索引庫
2.2.5.總結(jié)
3.文檔操作
3.1.新增文檔
3.2.查詢文檔
3.3.刪除文檔
3.4.修改文檔
3.4.1.全量修改
3.4.2.增量修改
3.5.總結(jié)
4.RestAPI
4.0.導(dǎo)入Demo工程
4.0.1.導(dǎo)入數(shù)據(jù)
4.0.2.導(dǎo)入項(xiàng)目
4.0.3.mapping映射分析
4.0.4.初始化RestClient
4.1.創(chuàng)建索引庫
4.1.1.代碼解讀
4.1.2.完整示例
4.2.刪除索引庫
4.3.判斷索引庫是否存在
4.4.總結(jié)
5.RestClient操作文檔
5.1.新增文檔
5.1.1.索引庫實(shí)體類
5.1.2.語法說明
5.1.3.完整代碼
5.2.查詢文檔
5.2.1.語法說明
5.2.2.完整代碼
5.3.刪除文檔
5.4.修改文檔
5.4.1.語法說明
5.4.2.完整代碼
5.5.批量導(dǎo)入文檔
5.5.1.語法說明
5.5.2.完整代碼
5.6.小結(jié)
1.初識elasticsearch
1.1.了解ES
1.1.1.elasticsearch的作用
elasticsearch是一款非常強(qiáng)大的開源搜索引擎,具備非常多強(qiáng)大功能,可以幫助我們從海量數(shù)據(jù)中快速找到需要的內(nèi)容
例如:
-
在GitHub搜索代碼
-
在電商網(wǎng)站搜索商品
-
在百度搜索答案
-
在打車軟件搜索附近的車
1.1.2.ELK技術(shù)棧
elasticsearch結(jié)合kibana、Logstash、Beats,也就是elastic stack(ELK)。被廣泛應(yīng)用在日志數(shù)據(jù)分析、實(shí)時監(jiān)控等領(lǐng)域:
而elasticsearch是elastic stack的核心,負(fù)責(zé)存儲、搜索、分析數(shù)據(jù)。
1.1.3.elasticsearch和lucene
elasticsearch底層是基于lucene來實(shí)現(xiàn)的。
Lucene是一個Java語言的搜索引擎類庫,是Apache公司的頂級項(xiàng)目,由DougCutting于1999年研發(fā)。官網(wǎng)地址:Apache Lucene - Welcome to Apache Lucene 。
elasticsearch的發(fā)展歷史:
-
2004年Shay Banon基于Lucene開發(fā)了Compass
-
2010年Shay Banon 重寫了Compass,取名為Elasticsearch。
1.1.4.為什么不是其他搜索技術(shù)?
目前比較知名的搜索引擎技術(shù)排名:
雖然在早期,Apache Solr是最主要的搜索引擎技術(shù),但隨著發(fā)展elasticsearch已經(jīng)漸漸超越了Solr,獨(dú)占鰲頭:
1.1.5.總結(jié)
什么是elasticsearch?
-
一個開源的分布式搜索引擎,可以用來實(shí)現(xiàn)搜索、日志統(tǒng)計、分析、系統(tǒng)監(jiān)控等功能
什么是elastic stack(ELK)?
-
是以elasticsearch為核心的技術(shù)棧,包括beats、Logstash、kibana、elasticsearch
什么是Lucene?
-
是Apache的開源搜索引擎類庫,提供了搜索引擎的核心API
1.2.倒排索引
倒排索引的概念是基于MySQL這樣的正向索引而言的。
1.2.1.正向索引
那么什么是正向索引呢?例如給下表(tb_goods)中的id創(chuàng)建索引:
如果是根據(jù)id查詢,那么直接走索引,查詢速度非???。
但如果是基于title做模糊查詢,只能是逐行掃描數(shù)據(jù),流程如下:
1)用戶搜索數(shù)據(jù),條件是title符合"%手機(jī)%"
2)逐行獲取數(shù)據(jù),比如id為1的數(shù)據(jù)
3)判斷數(shù)據(jù)中的title是否符合用戶搜索條件
4)如果符合則放入結(jié)果集,不符合則丟棄。回到步驟1
逐行掃描,也就是全表掃描,隨著數(shù)據(jù)量增加,其查詢效率也會越來越低。當(dāng)數(shù)據(jù)量達(dá)到數(shù)百萬時,就是一場災(zāi)難。
1.2.2.倒排索引
倒排索引中有兩個非常重要的概念:
-
文檔(
Document
):用來搜索的數(shù)據(jù),其中的每一條數(shù)據(jù)就是一個文檔。例如一個網(wǎng)頁、一個商品信息 -
詞條(
Term
):對文檔數(shù)據(jù)或用戶搜索數(shù)據(jù),利用某種算法分詞,得到的具備含義的詞語就是詞條。例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條
創(chuàng)建倒排索引是對正向索引的一種特殊處理,流程如下:
-
將每一個文檔的數(shù)據(jù)利用算法分詞,得到一個個詞條
-
創(chuàng)建表,每行數(shù)據(jù)包括詞條、詞條所在文檔id、位置等信息
-
因?yàn)樵~條唯一性,可以給詞條創(chuàng)建索引,例如hash表結(jié)構(gòu)索引
如圖:
倒排索引的搜索流程如下(以搜索"華為手機(jī)"為例):
1)用戶輸入條件"華為手機(jī)"
進(jìn)行搜索。
2)對用戶輸入內(nèi)容分詞,得到詞條:華為
、手機(jī)
。
3)拿著詞條在倒排索引中查找,可以得到包含詞條的文檔id:1、2、3。
4)拿著文檔id到正向索引中查找具體文檔。
如圖:
雖然要先查詢倒排索引,再查詢倒排索引,但是無論是詞條、還是文檔id都建立了索引,查詢速度非???!無需全表掃描。
1.2.3.正向和倒排
那么為什么一個叫做正向索引,一個叫做倒排索引呢?
-
正向索引是最傳統(tǒng)的,根據(jù)id索引的方式。但根據(jù)詞條查詢時,必須先逐條獲取每個文檔,然后判斷文檔中是否包含所需要的詞條,是根據(jù)文檔找詞條的過程。
-
而倒排索引則相反,是先找到用戶要搜索的詞條,根據(jù)詞條得到保護(hù)詞條的文檔的id,然后根據(jù)id獲取文檔。是根據(jù)詞條找文檔的過程。
是不是恰好反過來了?
那么兩者方式的優(yōu)缺點(diǎn)是什么呢?
正向索引:
-
優(yōu)點(diǎn):
-
可以給多個字段創(chuàng)建索引
-
根據(jù)索引字段搜索、排序速度非???/p>
-
-
缺點(diǎn):
-
根據(jù)非索引字段,或者索引字段中的部分詞條查找時,只能全表掃描。
-
倒排索引:
-
優(yōu)點(diǎn):
-
根據(jù)詞條搜索、模糊搜索時,速度非???/p>
-
-
缺點(diǎn):
-
只能給詞條創(chuàng)建索引,而不是字段
-
無法根據(jù)字段做排序
-
1.3.es的一些概念
elasticsearch中有很多獨(dú)有的概念,與mysql中略有差別,但也有相似之處。
1.3.1.文檔和字段
elasticsearch是面向文檔(Document)存儲的,可以是數(shù)據(jù)庫中的一條商品數(shù)據(jù),一個訂單信息。文檔數(shù)據(jù)會被序列化為json格式后存儲在elasticsearch中:
而Json文檔中往往包含很多的字段(Field),類似于數(shù)據(jù)庫中的列。
1.3.2.索引和映射
索引(Index),就是相同類型的文檔的集合。
例如:
-
所有用戶文檔,就可以組織在一起,稱為用戶的索引;
-
所有商品的文檔,可以組織在一起,稱為商品的索引;
-
所有訂單的文檔,可以組織在一起,稱為訂單的索引;
因此,我們可以把索引當(dāng)做是數(shù)據(jù)庫中的表。
數(shù)據(jù)庫的表會有約束信息,用來定義表的結(jié)構(gòu)、字段的名稱、類型等信息。因此,索引庫中就有映射(mapping),是索引中文檔的字段約束信息,類似表的結(jié)構(gòu)約束。
1.3.3.mysql與elasticsearch
我們統(tǒng)一的把mysql與elasticsearch的概念做一下對比:
MySQL | Elasticsearch | 說明 |
---|---|---|
Table | Index | 索引(index),就是文檔的集合,類似數(shù)據(jù)庫的表(table) |
Row | Document | 文檔(Document),就是一條條的數(shù)據(jù),類似數(shù)據(jù)庫中的行(Row),文檔都是JSON格式 |
Column | Field | 字段(Field),就是JSON文檔中的字段,類似數(shù)據(jù)庫中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文檔的約束,例如字段類型約束。類似數(shù)據(jù)庫的表結(jié)構(gòu)(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON風(fēng)格的請求語句,用來操作elasticsearch,實(shí)現(xiàn)CRUD |
是不是說,我們學(xué)習(xí)了elasticsearch就不再需要mysql了呢?
并不是如此,兩者各自有自己的擅長支出:
-
Mysql:擅長事務(wù)類型操作,可以確保數(shù)據(jù)的安全和一致性
-
Elasticsearch:擅長海量數(shù)據(jù)的搜索、分析、計算
因此在企業(yè)中,往往是兩者結(jié)合使用:
-
對安全性要求較高的寫操作,使用mysql實(shí)現(xiàn)
-
對查詢性能要求較高的搜索需求,使用elasticsearch實(shí)現(xiàn)
-
兩者再基于某種方式,實(shí)現(xiàn)數(shù)據(jù)的同步,保證一致性
1.4.安裝es、kibana
????????安裝es、kibana
1.4.1.總結(jié)
分詞器的作用是什么?
-
創(chuàng)建倒排索引時對文檔分詞
-
用戶搜索時,對輸入的內(nèi)容分詞
IK分詞器有幾種模式?
-
ik_smart:智能切分,粗粒度
-
ik_max_word:最細(xì)切分,細(xì)粒度
IK分詞器如何拓展詞條?如何停用詞條?
-
利用config目錄的IkAnalyzer.cfg.xml文件添加拓展詞典和停用詞典
-
在詞典中添加拓展詞條或者停用詞條
2.索引庫操作
索引庫就類似數(shù)據(jù)庫表,mapping映射就類似表的結(jié)構(gòu)。
我們要向es中存儲數(shù)據(jù),必須先創(chuàng)建“庫”和“表”。
2.1.mapping映射屬性
mapping是對索引庫中文檔的約束,常見的mapping屬性包括:
-
type:字段數(shù)據(jù)類型,常見的簡單類型有:
-
字符串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
-
數(shù)值:long、integer、short、byte、double、float、
-
布爾:boolean
-
日期:date
-
對象:object
-
-
index:是否創(chuàng)建索引,默認(rèn)為true
-
analyzer:使用哪種分詞器
-
properties:該字段的子字段
例如下面的json文檔:
{
????"age":?21,
????"weight":?52.1,
????"isMarried":?false,
????"info":?"黑馬程序員Java講師",
? ?"email":?"zy@itcast.cn",
? ?"score":?[99.1, 99.5, 98.9],
????"name":?{
????????"firstName":?"云",
????????"lastName":?"趙"
????}
}
對應(yīng)的每個字段映射(mapping):
-
age:類型為 integer;參與搜索,因此需要index為true;無需分詞器
-
weight:類型為float;參與搜索,因此需要index為true;無需分詞器
-
isMarried:類型為boolean;參與搜索,因此需要index為true;無需分詞器
-
info:類型為字符串,需要分詞,因此是text;參與搜索,因此需要index為true;分詞器可以用ik_smart
-
email:類型為字符串,但是不需要分詞,因此是keyword;不參與搜索,因此需要index為false;無需分詞器
-
score:雖然是數(shù)組,但是我們只看元素的類型,類型為float;參與搜索,因此需要index為true;無需分詞器
-
name:類型為object,需要定義多個子屬性
-
name.firstName;類型為字符串,但是不需要分詞,因此是keyword;參與搜索,因此需要index為true;無需分詞器
-
name.lastName;類型為字符串,但是不需要分詞,因此是keyword;參與搜索,因此需要index為true;無需分詞器
-
2.2.索引庫的CRUD
這里我們統(tǒng)一使用Kibana編寫DSL的方式來演示。
2.2.1.創(chuàng)建索引庫和映射
基本語法:
-
請求方式:PUT
-
請求路徑:/索引庫名,可以自定義
-
請求參數(shù):mapping映射
格式:
PUT?/索引庫名稱
{
??"mappings":?{
????"properties":?{
??????"字段名":{
????????"type":?"text",
????????"analyzer":?"ik_smart"
??????},
??????"字段名2":{
????????"type":?"keyword",
????????"index":?"false"
??????},
??????"字段名3":{
????????"properties":?{
??????????"子字段":?{
????????????"type":?"keyword"
??????????}
????????}
??????},
? ? ?// ...略
????}
??}
}
示例:
PUT?/heima
{
??"mappings":?{
????"properties":?{
??????"info":{
????????"type":?"text",
????????"analyzer":?"ik_smart"
??????},
??????"email":{
????????"type":?"keyword",
????????"index":?"falsae"
??????},
??????"name":{
????????"properties":?{
??????????"firstName":?{
????????????"type":?"keyword"
??????????}
????????}
??????},
? ? // ... 略
????}
??}
}
2.2.2.查詢索引庫
基本語法:
-
請求方式:GET
-
請求路徑:/索引庫名
-
請求參數(shù):無
格式:
GET /索引庫名
示例:
2.2.3.修改索引庫
倒排索引結(jié)構(gòu)雖然不復(fù)雜,但是一旦數(shù)據(jù)結(jié)構(gòu)改變(比如改變了分詞器),就需要重新創(chuàng)建倒排索引,這簡直是災(zāi)難。因此索引庫一旦創(chuàng)建,無法修改mapping。
雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因?yàn)椴粫Φ古潘饕a(chǎn)生影響。
語法說明:
PUT?/索引庫名/_mapping
{
??"properties":?{
????"新字段名":{
??????"type":?"integer"
????}
??}
}
示例:
2.2.4.刪除索引庫
語法:
-
請求方式:DELETE
-
請求路徑:/索引庫名
-
請求參數(shù):無
格式:
DELETE /索引庫名
在kibana中測試:
2.2.5.總結(jié)
索引庫操作有哪些?
-
創(chuàng)建索引庫:PUT /索引庫名
-
查詢索引庫:GET /索引庫名
-
刪除索引庫:DELETE /索引庫名
-
添加字段:PUT /索引庫名/_mapping
3.文檔操作
3.1.新增文檔
語法:
POST?/索引庫名/_doc/文檔id
{
????"字段1":?"值1",
????"字段2":?"值2",
????"字段3":?{
????????"子屬性1":?"值3",
????????"子屬性2":?"值4"
????},
? ?// ...
}
示例:
POST?/heima/_doc/1
{
????"info":?"黑馬程序員Java講師",
????"email":?"zy@itcast.cn",
????"name":?{
????????"firstName":?"云",
????????"lastName":?"趙"
????}
}
響應(yīng):
3.2.查詢文檔
根據(jù)rest風(fēng)格,新增是post,查詢應(yīng)該是get,不過查詢一般都需要條件,這里我們把文檔id帶上。
語法:
GET /{索引庫名稱}/_doc/{id}
通過kibana查看數(shù)據(jù):
GET /heima/_doc/1
查看結(jié)果:
3.3.刪除文檔
刪除使用DELETE請求,同樣,需要根據(jù)id進(jìn)行刪除:
語法:
DELETE /{索引庫名}/_doc/id值
示例:
# 根據(jù)id刪除數(shù)據(jù)
DELETE /heima/_doc/1
結(jié)果:
3.4.修改文檔
修改有兩種方式:
-
全量修改:直接覆蓋原來的文檔
-
增量修改:修改文檔中的部分字段
3.4.1.全量修改
全量修改是覆蓋原來的文檔,其本質(zhì)是:
-
根據(jù)指定的id刪除文檔
-
新增一個相同id的文檔
注意:如果根據(jù)id刪除時,id不存在,第二步的新增也會執(zhí)行,也就從修改變成了新增操作了。
語法:
PUT?/{索引庫名}/_doc/文檔id
{
????"字段1":?"值1",
????"字段2":?"值2",
? ?// ... 略
}
?
示例:
PUT?/heima/_doc/1
{
????"info":?"黑馬程序員高級Java講師",
????"email":?"zy@itcast.cn",
????"name":?{
????????"firstName":?"云",
????????"lastName":?"趙"
????}
}
3.4.2.增量修改
增量修改是只修改指定id匹配的文檔中的部分字段。
語法:
POST?/{索引庫名}/_update/文檔id
{
????"doc": {
? ? ? ? "字段名":?"新的值",
? }
}
示例:
POST?/heima/_update/1
{
??"doc":?{
????"email":?"ZhaoYun@itcast.cn"
??}
}
3.5.總結(jié)
文檔操作有哪些?
-
創(chuàng)建文檔:POST /{索引庫名}/_doc/文檔id { json文檔 }
-
查詢文檔:GET /{索引庫名}/_doc/文檔id
-
刪除文檔:DELETE /{索引庫名}/_doc/文檔id
-
修改文檔:
-
全量修改:PUT /{索引庫名}/_doc/文檔id { json文檔 }
-
增量修改:POST /{索引庫名}/_update/文檔id { "doc": {字段}}
-
4.RestAPI
ES官方提供了各種不同語言的客戶端,用來操作ES。這些客戶端的本質(zhì)就是組裝DSL語句,通過http請求發(fā)送給ES。官方文檔地址:Elasticsearch Clients | Elastic
其中的Java Rest Client又包括兩種:
-
Java Low Level Rest Client
-
Java High Level Rest Client
我們學(xué)習(xí)的是Java HighLevel Rest Client客戶端API
4.0.導(dǎo)入Demo工程
4.0.1.導(dǎo)入數(shù)據(jù)
首先導(dǎo)入課前資料提供的數(shù)據(jù)庫數(shù)據(jù):
數(shù)據(jù)結(jié)構(gòu)如下:
CREATE?TABLE?`tb_hotel`?(
??`id`?bigint(20)?NOT?NULL?COMMENT?'酒店id',
??`name`?varchar(255)?NOT?NULL?COMMENT?'酒店名稱;例:7天酒店',
??`address`?varchar(255)?NOT?NULL?COMMENT?'酒店地址;例:航頭路',
??`price`?int(10)?NOT?NULL?COMMENT?'酒店價格;例:329',
??`score`?int(2)?NOT?NULL?COMMENT?'酒店評分;例:45,就是4.5分',
??`brand`?varchar(32)?NOT?NULL?COMMENT?'酒店品牌;例:如家',
??`city`?varchar(32)?NOT?NULL?COMMENT?'所在城市;例:上海',
??`star_name`?varchar(16)?DEFAULT?NULL?COMMENT?'酒店星級,從低到高分別是:1星到5星,1鉆到5鉆',
??`business`?varchar(255)?DEFAULT?NULL?COMMENT?'商圈;例:虹橋',
??`latitude`?varchar(32)?NOT?NULL?COMMENT?'緯度;例:31.2497',
??`longitude`?varchar(32)?NOT?NULL?COMMENT?'經(jīng)度;例:120.3925',
??`pic`?varchar(255)?DEFAULT?NULL?COMMENT?'酒店圖片;例:/img/1.jpg',
??PRIMARY?KEY?(`id`)
)?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4;
4.0.2.導(dǎo)入項(xiàng)目
然后導(dǎo)入課前資料提供的項(xiàng)目:
項(xiàng)目結(jié)構(gòu)如圖:
4.0.3.mapping映射分析
創(chuàng)建索引庫,最關(guān)鍵的是mapping映射,而mapping映射要考慮的信息包括:
-
字段名
-
字段數(shù)據(jù)類型
-
是否參與搜索
-
是否需要分詞
-
如果分詞,分詞器是什么?
其中:
-
字段名、字段數(shù)據(jù)類型,可以參考數(shù)據(jù)表結(jié)構(gòu)的名稱和類型
-
是否參與搜索要分析業(yè)務(wù)來判斷,例如圖片地址,就無需參與搜索
-
是否分詞呢要看內(nèi)容,內(nèi)容如果是一個整體就無需分詞,反之則要分詞
-
分詞器,我們可以統(tǒng)一使用ik_max_word
來看下酒店數(shù)據(jù)的索引庫結(jié)構(gòu):
PUT /hotel
{
?"mappings": {
? ?"properties": {
? ? ?"id": {
? ? ? ?"type": "keyword"
? ? },
? ? ?"name":{
? ? ? ?"type": "text",
? ? ? ?"analyzer": "ik_max_word",
? ? ? ?"copy_to": "all"
? ? },
? ? ?"address":{
? ? ? ?"type": "keyword",
? ? ? ?"index": false
? ? },
? ? ?"price":{
? ? ? ?"type": "integer"
? ? },
? ? ?"score":{
? ? ? ?"type": "integer"
? ? },
? ? ?"brand":{
? ? ? ?"type": "keyword",
? ? ? ?"copy_to": "all"
? ? },
? ? ?"city":{
? ? ? ?"type": "keyword",
? ? ? ?"copy_to": "all"
? ? },
? ? ?"starName":{
? ? ? ?"type": "keyword"
? ? },
? ? ?"business":{
? ? ? ?"type": "keyword"
? ? },
? ? ?"location":{
? ? ? ?"type": "geo_point"
? ? },
? ? ?"pic":{
? ? ? ?"type": "keyword",
? ? ? ?"index": false
? ? },
? ? ?"all":{
? ? ? ?"type": "text",
? ? ? ?"analyzer": "ik_max_word"
? ? }
? }
}
}
幾個特殊字段說明:
-
location:地理坐標(biāo),里面包含精度、緯度
-
all:一個組合字段,其目的是將多字段的值 利用copy_to合并,提供給用戶搜索
地理坐標(biāo)說明:
copy_to說明:
4.0.4.初始化RestClient
在elasticsearch提供的API中,與elasticsearch一切交互都封裝在一個名為RestHighLevelClient的類中,必須先完成這個對象的初始化,建立與elasticsearch的連接。
分為三步:
1)引入es的RestHighLevelClient依賴:
<dependency>
? ?<groupId>org.elasticsearch.client</groupId>
? ?<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因?yàn)镾pringBoot默認(rèn)的ES版本是7.6.2,所以我們需要覆蓋默認(rèn)的ES版本:
<properties>
? ?<java.version>1.8</java.version>
? ?<elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
初始化的代碼如下:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
? ? ? ?HttpHost.create("http://192.168.150.101:9200")
));
這里為了單元測試方便,我們創(chuàng)建一個測試類HotelIndexTest,然后將初始化的代碼編寫在@BeforeEach方法中:
package cn.itcast.hotel;
?
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
?
import java.io.IOException;
?
public class HotelIndexTest {
? ?private RestHighLevelClient client;
?
? ?@BeforeEach
? ?void setUp() {
? ? ? ?this.client = new RestHighLevelClient(RestClient.builder(
? ? ? ? ? ? ? ?HttpHost.create("http://192.168.150.101:9200")
? ? ? ));
? }
?
? ?@AfterEach
? ?void tearDown() throws IOException {
? ? ? ?this.client.close();
? }
}
4.1.創(chuàng)建索引庫
4.1.1.代碼解讀
創(chuàng)建索引庫的API如下:
代碼分為三步:
-
1)創(chuàng)建Request對象。因?yàn)槭莿?chuàng)建索引庫的操作,因此Request是CreateIndexRequest。
-
2)添加請求參數(shù),其實(shí)就是DSL的JSON參數(shù)部分。因?yàn)閖son字符串很長,這里是定義了靜態(tài)字符串常量MAPPING_TEMPLATE,讓代碼看起來更加優(yōu)雅。
-
3)發(fā)送請求,client.indices()方法的返回值是IndicesClient類型,封裝了所有與索引庫操作有關(guān)的方法。
4.1.2.完整示例
在hotel-demo的cn.itcast.hotel.constants包下,創(chuàng)建一個類,定義mapping映射的JSON字符串常量:
package cn.itcast.hotel.constants;
?
public class HotelConstants {
? ?public static final String MAPPING_TEMPLATE = "{\n" +
? ? ? ? ? ?" \"mappings\": {\n" +
? ? ? ? ? ?" ? \"properties\": {\n" +
? ? ? ? ? ?" ? ? \"id\": {\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"name\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"text\",\n" +
? ? ? ? ? ?" ? ? ? \"analyzer\": \"ik_max_word\",\n" +
? ? ? ? ? ?" ? ? ? \"copy_to\": \"all\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"address\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\",\n" +
? ? ? ? ? ?" ? ? ? \"index\": false\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"price\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"integer\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"score\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"integer\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"brand\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\",\n" +
? ? ? ? ? ?" ? ? ? \"copy_to\": \"all\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"city\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\",\n" +
? ? ? ? ? ?" ? ? ? \"copy_to\": \"all\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"starName\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"business\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"location\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"geo_point\"\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"pic\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"keyword\",\n" +
? ? ? ? ? ?" ? ? ? \"index\": false\n" +
? ? ? ? ? ?" ? ? },\n" +
? ? ? ? ? ?" ? ? \"all\":{\n" +
? ? ? ? ? ?" ? ? ? \"type\": \"text\",\n" +
? ? ? ? ? ?" ? ? ? \"analyzer\": \"ik_max_word\"\n" +
? ? ? ? ? ?" ? ? }\n" +
? ? ? ? ? ?" ? }\n" +
? ? ? ? ? ?" }\n" +
? ? ? ? ? ?"}";
}
在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實(shí)現(xiàn)創(chuàng)建索引:
@Test
void createHotelIndex() throws IOException {
? ?// 1.創(chuàng)建Request對象
? ?CreateIndexRequest request = new CreateIndexRequest("hotel");
? ?// 2.準(zhǔn)備請求的參數(shù):DSL語句
? ?request.source(MAPPING_TEMPLATE, XContentType.JSON);
? ?// 3.發(fā)送請求
? ?client.indices().create(request, RequestOptions.DEFAULT);
}
4.2.刪除索引庫
刪除索引庫的DSL語句非常簡單:
DELETE /hotel
與創(chuàng)建索引庫相比:
-
請求方式從PUT變?yōu)镈ELTE
-
請求路徑不變
-
無請求參數(shù)
所以代碼的差異,注意體現(xiàn)在Request對象上。依然是三步走:
-
1)創(chuàng)建Request對象。這次是DeleteIndexRequest對象
-
2)準(zhǔn)備參數(shù)。這里是無參
-
3)發(fā)送請求。改用delete方法
在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實(shí)現(xiàn)刪除索引:
@Test
void testDeleteHotelIndex() throws IOException {
? ?// 1.創(chuàng)建Request對象
? ?DeleteIndexRequest request = new DeleteIndexRequest("hotel");
? ?// 2.發(fā)送請求
? ?client.indices().delete(request, RequestOptions.DEFAULT);
}
4.3.判斷索引庫是否存在
判斷索引庫是否存在,本質(zhì)就是查詢,對應(yīng)的DSL是:
GET /hotel
因此與刪除的Java代碼流程是類似的。依然是三步走:
-
1)創(chuàng)建Request對象。這次是GetIndexRequest對象
-
2)準(zhǔn)備參數(shù)。這里是無參
-
3)發(fā)送請求。改用exists方法
@Test
void testExistsHotelIndex() throws IOException {
? ?// 1.創(chuàng)建Request對象
? ?GetIndexRequest request = new GetIndexRequest("hotel");
? ?// 2.發(fā)送請求
? ?boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
? ?// 3.輸出
? ?System.err.println(exists ? "索引庫已經(jīng)存在!" : "索引庫不存在!");
}
4.4.總結(jié)
JavaRestClient操作elasticsearch的流程基本類似。核心是client.indices()方法來獲取索引庫的操作對象。
索引庫操作的基本步驟:
-
初始化RestHighLevelClient
-
創(chuàng)建XxxIndexRequest。XXX是Create、Get、Delete
-
準(zhǔn)備DSL( Create時需要,其它是無參)
-
發(fā)送請求。調(diào)用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
5.RestClient操作文檔
為了與索引庫操作分離,我們再次參加一個測試類,做兩件事情:
-
初始化RestHighLevelClient
-
我們的酒店數(shù)據(jù)在數(shù)據(jù)庫,需要利用IHotelService去查詢,所以注入這個接口
package cn.itcast.hotel;
?
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.service.IHotelService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
?
import java.io.IOException;
import java.util.List;
?
@SpringBootTest
public class HotelDocumentTest {
? ?@Autowired
? ?private IHotelService hotelService;
?
? ?private RestHighLevelClient client;
?
? ?@BeforeEach
? ?void setUp() {
? ? ? ?this.client = new RestHighLevelClient(RestClient.builder(
? ? ? ? ? ? ? ?HttpHost.create("http://192.168.150.101:9200")
? ? ? ));
? }
?
? ?@AfterEach
? ?void tearDown() throws IOException {
? ? ? ?this.client.close();
? }
}
?
5.1.新增文檔
我們要將數(shù)據(jù)庫的酒店數(shù)據(jù)查詢出來,寫入elasticsearch中。
5.1.1.索引庫實(shí)體類
數(shù)據(jù)庫查詢后的結(jié)果是一個Hotel類型的對象。結(jié)構(gòu)如下:
@Data
@TableName("tb_hotel")
public class Hotel {
? ?@TableId(type = IdType.INPUT)
? ?private Long id;
? ?private String name;
? ?private String address;
? ?private Integer price;
? ?private Integer score;
? ?private String brand;
? ?private String city;
? ?private String starName;
? ?private String business;
? ?private String longitude;
? ?private String latitude;
? ?private String pic;
}
與我們的索引庫結(jié)構(gòu)存在差異:
-
longitude和latitude需要合并為location
因此,我們需要定義一個新的類型,與索引庫結(jié)構(gòu)吻合:
package cn.itcast.hotel.pojo;
?
import lombok.Data;
import lombok.NoArgsConstructor;
?
@Data
@NoArgsConstructor
public class HotelDoc {
? ?private Long id;
? ?private String name;
? ?private String address;
? ?private Integer price;
? ?private Integer score;
? ?private String brand;
? ?private String city;
? ?private String starName;
? ?private String business;
? ?private String location;
? ?private String pic;
?
? ?public HotelDoc(Hotel hotel) {
? ? ? ?this.id = hotel.getId();
? ? ? ?this.name = hotel.getName();
? ? ? ?this.address = hotel.getAddress();
? ? ? ?this.price = hotel.getPrice();
? ? ? ?this.score = hotel.getScore();
? ? ? ?this.brand = hotel.getBrand();
? ? ? ?this.city = hotel.getCity();
? ? ? ?this.starName = hotel.getStarName();
? ? ? ?this.business = hotel.getBusiness();
? ? ? ?this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
? ? ? ?this.pic = hotel.getPic();
? }
}
5.1.2.語法說明
新增文檔的DSL語句如下:
POST /{索引庫名}/_doc/1
{
? ?"name": "Jack",
? ?"age": 21
}
對應(yīng)的java代碼如圖:
可以看到與創(chuàng)建索引庫類似,同樣是三步走:
-
1)創(chuàng)建Request對象
-
2)準(zhǔn)備請求參數(shù),也就是DSL中的JSON文檔
-
3)發(fā)送請求
變化的地方在于,這里直接使用client.xxx()的API,不再需要client.indices()了。
5.1.3.完整代碼
我們導(dǎo)入酒店數(shù)據(jù),基本流程一致,但是需要考慮幾點(diǎn)變化:
-
酒店數(shù)據(jù)來自于數(shù)據(jù)庫,我們需要先查詢出來,得到hotel對象
-
hotel對象需要轉(zhuǎn)為HotelDoc對象
-
HotelDoc需要序列化為json格式
因此,代碼整體步驟如下:
-
1)根據(jù)id查詢酒店數(shù)據(jù)Hotel
-
2)將Hotel封裝為HotelDoc
-
3)將HotelDoc序列化為JSON
-
4)創(chuàng)建IndexRequest,指定索引庫名和id
-
5)準(zhǔn)備請求參數(shù),也就是JSON文檔
-
6)發(fā)送請求
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testAddDocument() throws IOException {
? ?// 1.根據(jù)id查詢酒店數(shù)據(jù)
? ?Hotel hotel = hotelService.getById(61083L);
? ?// 2.轉(zhuǎn)換為文檔類型
? ?HotelDoc hotelDoc = new HotelDoc(hotel);
? ?// 3.將HotelDoc轉(zhuǎn)json
? ?String json = JSON.toJSONString(hotelDoc);
?
? ?// 1.準(zhǔn)備Request對象
? ?IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
? ?// 2.準(zhǔn)備Json文檔
? ?request.source(json, XContentType.JSON);
? ?// 3.發(fā)送請求
? ?client.index(request, RequestOptions.DEFAULT);
}
5.2.查詢文檔
5.2.1.語法說明
查詢的DSL語句如下:
GET /hotel/_doc/{id}
非常簡單,因此代碼大概分兩步:
-
準(zhǔn)備Request對象
-
發(fā)送請求
不過查詢的目的是得到結(jié)果,解析為HotelDoc,因此難點(diǎn)是結(jié)果的解析。完整代碼如下:
可以看到,結(jié)果是一個JSON,其中文檔放在一個_source
屬性中,因此解析就是拿到_source
,反序列化為Java對象即可。
與之前類似,也是三步走:
-
1)準(zhǔn)備Request對象。這次是查詢,所以是GetRequest
-
2)發(fā)送請求,得到結(jié)果。因?yàn)槭遣樵?,這里調(diào)用client.get()方法
-
3)解析結(jié)果,就是對JSON做反序列化
5.2.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testGetDocumentById() throws IOException {
? ?// 1.準(zhǔn)備Request
? ?GetRequest request = new GetRequest("hotel", "61082");
? ?// 2.發(fā)送請求,得到響應(yīng)
? ?GetResponse response = client.get(request, RequestOptions.DEFAULT);
? ?// 3.解析響應(yīng)結(jié)果
? ?String json = response.getSourceAsString();
?
? ?HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
? ?System.out.println(hotelDoc);
}
5.3.刪除文檔
刪除的DSL為是這樣的:
DELETE /hotel/_doc/{id}
與查詢相比,僅僅是請求方式從DELETE變成GET,可以想象Java代碼應(yīng)該依然是三步走:
-
1)準(zhǔn)備Request對象,因?yàn)槭莿h除,這次是DeleteRequest對象。要指定索引庫名和id
-
2)準(zhǔn)備參數(shù),無參
-
3)發(fā)送請求。因?yàn)槭莿h除,所以是client.delete()方法
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testDeleteDocument() throws IOException {
? ?// 1.準(zhǔn)備Request
? ?DeleteRequest request = new DeleteRequest("hotel", "61083");
? ?// 2.發(fā)送請求
? ?client.delete(request, RequestOptions.DEFAULT);
}
5.4.修改文檔
5.4.1.語法說明
修改我們講過兩種方式:
-
全量修改:本質(zhì)是先根據(jù)id刪除,再新增
-
增量修改:修改文檔中的指定字段值
在RestClient的API中,全量修改與新增的API完全一致,判斷依據(jù)是ID:
-
如果新增時,ID已經(jīng)存在,則修改
-
如果新增時,ID不存在,則新增
這里不再贅述,我們主要關(guān)注增量修改。
代碼示例如圖:
與之前類似,也是三步走:
-
1)準(zhǔn)備Request對象。這次是修改,所以是UpdateRequest
-
2)準(zhǔn)備參數(shù)。也就是JSON文檔,里面包含要修改的字段
-
3)更新文檔。這里調(diào)用client.update()方法
5.4.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testUpdateDocument() throws IOException {
? ?// 1.準(zhǔn)備Request
? ?UpdateRequest request = new UpdateRequest("hotel", "61083");
? ?// 2.準(zhǔn)備請求參數(shù)
? ?request.doc(
? ? ? ?"price", "952",
? ? ? ?"starName", "四鉆"
? );
? ?// 3.發(fā)送請求
? ?client.update(request, RequestOptions.DEFAULT);
}
5.5.批量導(dǎo)入文檔
案例需求:利用BulkRequest批量將數(shù)據(jù)庫數(shù)據(jù)導(dǎo)入到索引庫中。
步驟如下:
-
利用mybatis-plus查詢酒店數(shù)據(jù)
-
將查詢到的酒店數(shù)據(jù)(Hotel)轉(zhuǎn)換為文檔類型數(shù)據(jù)(HotelDoc)
-
利用JavaRestClient中的BulkRequest批處理,實(shí)現(xiàn)批量新增文檔
5.5.1.語法說明
批量處理BulkRequest,其本質(zhì)就是將多個普通的CRUD請求組合在一起發(fā)送。
其中提供了一個add方法,用來添加其他請求:
可以看到,能添加的請求包括:
-
IndexRequest,也就是新增
-
UpdateRequest,也就是修改
-
DeleteRequest,也就是刪除
因此Bulk中添加了多個IndexRequest,就是批量新增功能了。示例:
其實(shí)還是三步走:
-
1)創(chuàng)建Request對象。這里是BulkRequest
-
2)準(zhǔn)備參數(shù)。批處理的參數(shù),就是其它Request對象,這里就是多個IndexRequest
-
3)發(fā)起請求。這里是批處理,調(diào)用的方法為client.bulk()方法
我們在導(dǎo)入酒店數(shù)據(jù)時,將上述代碼改造成for循環(huán)處理即可。
5.5.2.完整代碼
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testBulkRequest() throws IOException {
? ?// 批量查詢酒店數(shù)據(jù)
? ?List<Hotel> hotels = hotelService.list();
?
? ?// 1.創(chuàng)建Request
? ?BulkRequest request = new BulkRequest();
? ?// 2.準(zhǔn)備參數(shù),添加多個新增的Request
? ?for (Hotel hotel : hotels) {
? ? ? ?// 2.1.轉(zhuǎn)換為文檔類型HotelDoc
? ? ? ?HotelDoc hotelDoc = new HotelDoc(hotel);
? ? ? ?// 2.2.創(chuàng)建新增文檔的Request對象
? ? ? ?request.add(new IndexRequest("hotel")
? ? ? ? ? ? ? ? ? .id(hotelDoc.getId().toString())
? ? ? ? ? ? ? ? ? .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
? }
? ?// 3.發(fā)送請求
? ?client.bulk(request, RequestOptions.DEFAULT);
}
5.6.小結(jié)
文檔操作的基本步驟:
-
初始化RestHighLevelClient
-
創(chuàng)建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
-
準(zhǔn)備參數(shù)(Index、Update、Bulk時需要)
-
發(fā)送請求。調(diào)用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk文章來源:http://www.zghlxwxcb.cn/news/detail-775962.html
-
解析結(jié)果(Get時需要)文章來源地址http://www.zghlxwxcb.cn/news/detail-775962.html
到了這里,關(guān)于Elasticsearch入門使用教程 _1的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!