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