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)地址:https://lucene.apache.org/ 。
elasticsearch 的發(fā)展歷史:
- 2004年Shay Banon基于Lucene開發(fā)了Compass
- 2010年Shay Banon 重寫了Compass,取名為Elasticsearch。
1.1.4.為什么不是其他搜索技術(shù)?
目前比較知名的搜索引擎技術(shù)排名:
1.1.5.總結(jié)
什么是 elasticsearch?
- 一個開源的分布式搜索引擎,可以用來實(shí)現(xiàn)搜索、日志統(tǒng)計(jì)、分析、系統(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ù),流程如下:
- 用戶搜索數(shù)據(jù),條件是 title 符合
"%手機(jī)%"
- 逐行獲取數(shù)據(jù),比如 id 為 1 的數(shù)據(jù)
- 判斷數(shù)據(jù)中的 title 是否符合用戶搜索條件
- 如果符合則放入結(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ī)"為例):
- 用戶輸入條件
"華為手機(jī)"
進(jìn)行搜索。- 用戶輸入內(nèi)容分詞,得到詞條:
華為
、手機(jī)
。- 拿著詞條在倒排索引中查找,可以得到包含詞條的文檔id:1、2、3。
- 拿著文檔 id 到正向索引中查找具體文檔。
如圖:
雖然要先查詢倒排索引,再查詢倒排索引,但是無論是詞條、還是文檔 id 都建立了索引,查詢速度非???!無需全表掃描。
1.2.3.正向和倒排
那么為什么一個叫做正向索引,一個叫做倒排索引呢?
- 正向索引是最傳統(tǒng)的,根據(jù) id 索引的方式。但根據(jù)詞條查詢時,必須先逐條獲取每個文檔,然后判斷文檔中是否包含所需要的詞條,是根據(jù)文檔找詞條的過程。
- 而倒排索引則相反,是先找到用戶要搜索的詞條,根據(jù)詞條得到保護(hù)詞條的文檔的 id,然后根據(jù)id獲取文檔。是根據(jù)詞條找文檔的過程。
正向索引:
- 優(yōu)點(diǎn):
- 可以給多個字段創(chuàng)建索引
- 根據(jù)索引字段搜索、排序速度非???/li>
- 缺點(diǎn):
- 根據(jù)非索引字段,或者索引字段中的部分詞條查找時,只能全表掃描。
倒排索引:
- 優(yōu)點(diǎn):
- 根據(jù)詞條搜索、模糊搜索時,速度非???/li>
- 缺點(diǎn):
- 只能給詞條創(chuàng)建索引,而不是字段
- 無法根據(jù)字段做排序
1.3.es的一些概念
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 |
- Mysql:擅長事務(wù)類型操作,可以確保數(shù)據(jù)的安全和一致性
- Elasticsearch:擅長海量數(shù)據(jù)的搜索、分析、計(jì)算
因此在企業(yè)中,往往是兩者結(jié)合使用:
- 對安全性要求較高的寫操作,使用mysql實(shí)現(xiàn)
- 對查詢性能要求較高的搜索需求,使用elasticsearch實(shí)現(xiàn)
- 兩者再基于某種方式,實(shí)現(xiàn)數(shù)據(jù)的同步,保證一致性
1.4.安裝 es、kibana
1.4.1.安裝
參考課前資料:
安裝elasticsearch.md
1.4.1.1 部署單點(diǎn) es
(1)創(chuàng)建網(wǎng)絡(luò)
因?yàn)槲覀冞€需要部署 kibana 容器,因此需要讓 es 和 kibana 容器互聯(lián)。這里先創(chuàng)建一個網(wǎng)絡(luò):
docker network create es-net
(2)加載鏡像
這里我們采用 elasticsearch 的 7.12.1 版本的鏡像,這個鏡像體積非常大,接近1G。不建議大家自己 pull。
課前資料提供了鏡像的tar包,大家將其上傳到虛擬機(jī)中,然后運(yùn)行命令加載即可:
# 導(dǎo)入數(shù)據(jù)
docker load -i es.tar
(3)運(yùn)行
運(yùn)行 docker 命令,部署單點(diǎn)es:
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
1.4.1.2 部署 kibana
(1)部署
運(yùn)行 docker 命令,部署 kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net
:加入一個名為 es-net 的網(wǎng)絡(luò)中,與 elasticsearch 在同一個網(wǎng)絡(luò)中-e ELASTICSEARCH_HOSTS=http://es:9200"
:設(shè)置 elasticsearch 的地址,因?yàn)?kibana 已經(jīng)與 elasticsearch 在一個網(wǎng)絡(luò),因此可以用容器名直接訪問elasticsearch-p 5601:5601
:端口映射配置
可以通過命令:
docker logs -f kibana
查看運(yùn)行日志,當(dāng)查看到下面的日志,說明成功:
此時,在瀏覽器輸入地址訪問:http://192.168.150.101:5601,即可看到結(jié)果
(2)DevTools
kibana 中提供了一個 DevTools 界面:
這個界面中可以編寫DSL來操作elasticsearch。并且對DSL語句有自動補(bǔ)全功能。
1.4.2.分詞器
es 在創(chuàng)建倒排索引時需要對文檔分詞;在搜索時,需要對用戶輸入內(nèi)容分詞。但默認(rèn)的分詞規(guī)則對中文處理并不友好。
我們在kibana的DevTools中測試:
POST /_analyze
{
"analyzer": "standard",
"text": "黑馬程序員學(xué)習(xí)java太棒了!"
}
語法說明:
- POST:請求方式
- /_analyze:請求路徑,這里省略了http://192.168.150.101:9200,有kibana幫我們補(bǔ)充
- 請求參數(shù),json風(fēng)格:
- analyzer:分詞器類型,這里是默認(rèn)的standard分詞器
- text:要分詞的內(nèi)容
處理中文分詞,一般會使用 IK 分詞器。https://github.com/medcl/elasticsearch-analysis-ik
安裝 IK 分詞器,參考課前資料
《安裝elasticsearch.md》
ik 分詞器包含兩種模式:
- ik_smart:最少切分,粗粒度
- ik_max_word:最細(xì)切分,細(xì)粒度
ik分詞器-拓展詞庫
要拓展ik分詞器的詞庫,只需要修改一個ik分詞器目錄中的config目錄中的IkAnalyzer.cfg.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典 *** 添加擴(kuò)展詞典-->
<entry key="ext_dict">ext.dic</entry>
</properties>
然后在名為ext.dic的文件中,添加想要拓展的詞語即可:
傳智播客
奧力給
ik分詞器-停用詞庫
要禁用某些敏感詞條,只需要修改一個ik分詞器目錄中的config目錄中的IkAnalyzer.cfg.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 擴(kuò)展配置</comment>
<!--用戶可以在這里配置自己的擴(kuò)展字典-->
<entry key="ext_dict">ext.dic</entry>
<!--用戶可以在這里配置自己的擴(kuò)展停止詞字典 *** 添加停用詞詞典-->
<entry key="ext_stopwords">stopword.dic</entry>
</properties>
然后在名為stopword.dic的文件中,添加想要拓展的詞語即可:
敏感詞匯
1.4.3.總結(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。
官方文檔地址: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.導(dǎo)入Demo工程
4.0.1.導(dǎo)入數(shù)據(jù)
首先導(dǎo)入課前資料提供的數(shù)據(jù)庫數(shù)據(jù):
tb_hotel.sql
數(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)目:
hotel-demo
項(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)?DELTE
- 請求路徑不變
- 無請求參數(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測試類中,編寫單元測試:文章來源:http://www.zghlxwxcb.cn/news/detail-850304.html
@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é)
文檔操作的基本步驟:文章來源地址http://www.zghlxwxcb.cn/news/detail-850304.html
- 初始化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
- 解析結(jié)果(Get時需要)
到了這里,關(guān)于day05-Elasticsearch01的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!