ElasticSearch基礎學習
分布式搜尋引擎01 -- elasticsearch基礎
0.學習目标
1.初識elasticsearch
1.1.了解ES
1.1.1.elasticsearch的作用
elasticsearch是一款非常強大的開源搜尋引擎,具備非常多強大功能,可以幫助我們從海量資料中快速找到需要的内容
例如:

- 在GitHub搜尋代碼
【分布式搜尋引擎】elasticsearch基礎 - 在電商網站搜尋商品
【分布式搜尋引擎】elasticsearch基礎 - 在百度搜尋答案
【分布式搜尋引擎】elasticsearch基礎 - 在打車軟體搜尋附近的車
【分布式搜尋引擎】elasticsearch基礎
1.1.2.ELK技術棧
elasticsearch結合kibana、Logstash、Beats,也就是elastic stack(ELK)。被廣泛應用在日志資料分析、實時監控等領域:
而elasticsearch是elastic stack的核心,負責存儲、搜尋、分析資料。
1.1.3.elasticsearch和lucene
elasticsearch底層是基于lucene來實作的。
Lucene是一個Java語言的搜尋引擎類庫,是Apache公司的頂級項目,由DougCutting于1999年研發。官網位址:https://lucene.apache.org/ 。
elasticsearch的發展曆史:
- 2004年Shay Banon基于Lucene開發了Compass
- 2010年Shay Banon 重寫了Compass,取名為Elasticsearch。
1.1.4.為什麼不是其他搜尋技術?
目前比較知名的搜尋引擎技術排名:
雖然在早期,Apache Solr是最主要的搜尋引擎技術,但随着發展elasticsearch已經漸漸超越了Solr,獨占鳌頭:
1.1.5.總結
什麼是elasticsearch?
- 一個開源的分布式搜尋引擎,可以用來實作搜尋、日志統計、分析、系統監控等功能
什麼是elastic stack(ELK)?
- 是以elasticsearch為核心的技術棧,包括beats、Logstash、kibana、elasticsearch
什麼是Lucene?
- 是Apache的開源搜尋引擎類庫,提供了搜尋引擎的核心API
1.2.反向索引
反向索引的概念是基于MySQL這樣的正向索引而言的。
1.2.1.正向索引
那麼什麼是正向索引呢?例如給下表(tb_goods)中的id建立索引:
如果是根據id查詢,那麼直接走索引,查詢速度非常快。
但如果是基于title做模糊查詢,隻能是逐行掃描資料,流程如下:
1)使用者搜尋資料,條件是title符合
"%手機%"
2)逐行擷取資料,比如id為1的資料
3)判斷資料中的title是否符合使用者搜尋條件
4)如果符合則放入結果集,不符合則丢棄。回到步驟1
逐行掃描,也就是全表掃描,随着資料量增加,其查詢效率也會越來越低。當資料量達到數百萬時,就是一場災難。
1.2.2.反向索引
反向索引中有兩個非常重要的概念:
- 文檔(
):用來搜尋的資料,其中的每一條資料就是一個文檔。例如一個網頁、一個商品資訊Document
- 詞條(
):對文檔資料或使用者搜尋資料,利用某種算法分詞,得到的具備含義的詞語就是詞條。例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條Term
建立反向索引是對正向索引的一種特殊處理,流程如下:
- 将每一個文檔的資料利用算法分詞,得到一個個詞條
- 建立表,每行資料包括詞條、詞條所在文檔id、位置等資訊
- 因為詞條唯一性,可以給詞條建立索引,例如hash表結構索引
如圖:
反向索引的搜尋流程如下(以搜尋"華為手機"為例):
1)使用者輸入條件
"華為手機"
進行搜尋。
2)對使用者輸入内容分詞,得到詞條:
華為
、
手機
。
3)拿着詞條在反向索引中查找,可以得到包含詞條的文檔id:1、2、3。
4)拿着文檔id到正向索引中查找具體文檔。
雖然要先查詢反向索引,再查詢反向索引,但是無論是詞條、還是文檔id都建立了索引,查詢速度非常快!無需全表掃描。
1.2.3.正向和倒排
那麼為什麼一個叫做正向索引,一個叫做反向索引呢?
- 正向索引是最傳統的,根據id索引的方式。但根據詞條查詢時,必須先逐條擷取每個文檔,然後判斷文檔中是否包含所需要的詞條,是根據文檔找詞條的過程。
- 而反向索引則相反,是先找到使用者要搜尋的詞條,根據詞條得到保護詞條的文檔的id,然後根據id擷取文檔。是根據詞條找文檔的過程。
是不是恰好反過來了?
那麼兩者方式的優缺點是什麼呢?
正向索引:
- 優點:
- 可以給多個字段建立索引
- 根據索引字段搜尋、排序速度非常快
- 缺點:
- 根據非索引字段,或者索引字段中的部分詞條查找時,隻能全表掃描。
反向索引:
-
- 根據詞條搜尋、模糊搜尋時,速度非常快
-
- 隻能給詞條建立索引,而不是字段
- 無法根據字段做排序
1.3.es的一些概念
elasticsearch中有很多獨有的概念,與mysql中略有差别,但也有相似之處。
1.3.1.文檔和字段
elasticsearch是面向文檔(Document)存儲的,可以是資料庫中的一條商品資料,一個訂單資訊。文檔資料會被序列化為json格式後存儲在elasticsearch中:
而Json文檔中往往包含很多的字段(Field),類似于資料庫中的列。
1.3.2.索引和映射
索引(Index),就是相同類型的文檔的集合。
- 所有使用者文檔,就可以組織在一起,稱為使用者的索引;
- 所有商品的文檔,可以組織在一起,稱為商品的索引;
- 所有訂單的文檔,可以組織在一起,稱為訂單的索引;
是以,我們可以把索引當做是資料庫中的表。
資料庫的表會有限制資訊,用來定義表的結構、字段的名稱、類型等資訊。是以,索引庫中就有映射(mapping),是索引中文檔的字段限制資訊,類似表的結構限制。
1.3.3.mysql與elasticsearch
我們統一的把mysql與elasticsearch的概念做一下對比:
MySQL | Elasticsearch | 說明 |
---|---|---|
Table | Index | 索引(index),就是文檔的集合,類似資料庫的表(table) |
Row | Document | 文檔(Document),就是一條條的資料,類似資料庫中的行(Row),文檔都是JSON格式 |
Column | Field | 字段(Field),就是JSON文檔中的字段,類似資料庫中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文檔的限制,例如字段類型限制。類似資料庫的表結構(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON風格的請求語句,用來操作elasticsearch,實作CRUD |
是不是說,我們學習了elasticsearch就不再需要mysql了呢?
并不是如此,兩者各自有自己的擅長支出:
- Mysql:擅長事務類型操作,可以確定資料的安全和一緻性
- Elasticsearch:擅長海量資料的搜尋、分析、計算
是以在企業中,往往是兩者結合使用:
- 對安全性要求較高的寫操作,使用mysql實作
- 對查詢性能要求較高的搜尋需求,使用elasticsearch實作
- 兩者再基于某種方式,實作資料的同步,保證一緻性
1.4.安裝es、kibana
1.4.1.安裝
參考課前資料:
1.4.2.分詞器
1.4.3.總結
分詞器的作用是什麼?
- 建立反向索引時對文檔分詞
- 使用者搜尋時,對輸入的内容分詞
IK分詞器有幾種模式?
- ik_smart:智能切分,粗粒度
- ik_max_word:最細切分,細粒度
IK分詞器如何拓展詞條?如何停用詞條?
- 利用config目錄的IkAnalyzer.cfg.xml檔案添加拓展詞典和停用詞典
- 在詞典中添加拓展詞條或者停用詞條
2.索引庫操作
索引庫就類似資料庫表,mapping映射就類似表的結構。
我們要向es中存儲資料,必須先建立“庫”和“表”。
2.1.mapping映射屬性
mapping是對索引庫中文檔的限制,常見的mapping屬性包括:
- type:字段資料類型,常見的簡單類型有:
- 字元串:text(可分詞的文本)、keyword(精确值,例如:品牌、國家、ip位址)
- 數值:long、integer、short、byte、double、float、
- 布爾:boolean
- 日期:date
- 對象:object
- index:是否建立索引,預設為true
- analyzer:使用哪種分詞器
- properties:該字段的子字段
例如下面的json文檔:
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "黑馬程式員Java講師",
"email": "[email protected]",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "雲",
"lastName": "趙"
}
}
對應的每個字段映射(mapping):
- age:類型為 integer;參與搜尋,是以需要index為true;無需分詞器
- weight:類型為float;參與搜尋,是以需要index為true;無需分詞器
- isMarried:類型為boolean;參與搜尋,是以需要index為true;無需分詞器
- info:類型為字元串,需要分詞,是以是text;參與搜尋,是以需要index為true;分詞器可以用ik_smart
- email:類型為字元串,但是不需要分詞,是以是keyword;不參與搜尋,是以需要index為false;無需分詞器
- score:雖然是數組,但是我們隻看元素的類型,類型為float;參與搜尋,是以需要index為true;無需分詞器
- name:類型為object,需要定義多個子屬性
- name.firstName;類型為字元串,但是不需要分詞,是以是keyword;參與搜尋,是以需要index為true;無需分詞器
- name.lastName;類型為字元串,但是不需要分詞,是以是keyword;參與搜尋,是以需要index為true;無需分詞器
2.2.索引庫的CRUD
這裡我們統一使用Kibana編寫DSL的方式來示範。
2.2.1.建立索引庫和映射
基本文法:
- 請求方式:PUT
- 請求路徑:/索引庫名,可以自定義
- 請求參數: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
- 請求路徑:/索引庫名
- 請求參數:無
GET /索引庫名
2.2.3.修改索引庫
反向索引結構雖然不複雜,但是一旦資料結構改變(比如改變了分詞器),就需要重新建立反向索引,這簡直是災難。是以索引庫一旦建立,無法修改mapping。
雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因為不會對反向索引産生影響。
文法說明:
PUT /索引庫名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
2.2.4.删除索引庫
文法:
- 請求方式:DELETE
DELETE /索引庫名
在kibana中測試:
2.2.5.總結
索引庫操作有哪些?
- 建立索引庫: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": "[email protected]",
"name": {
"firstName": "雲",
"lastName": "趙"
}
}
響應:
3.2.查詢文檔
根據rest風格,新增是post,查詢應該是get,不過查詢一般都需要條件,這裡我們把文檔id帶上。
GET /{索引庫名稱}/_doc/{id}
通過kibana檢視資料:
GET /heima/_doc/1
檢視結果:
3.3.删除文檔
删除使用DELETE請求,同樣,需要根據id進行删除:
DELETE /{索引庫名}/_doc/id值
# 根據id删除資料
DELETE /heima/_doc/1
結果:
3.4.修改文檔
修改有兩種方式:
- 全量修改:直接覆寫原來的文檔
- 增量修改:修改文檔中的部分字段
3.4.1.全量修改
全量修改是覆寫原來的文檔,其本質是:
- 根據指定的id删除文檔
- 新增一個相同id的文檔
注意:如果根據id删除時,id不存在,第二步的新增也會執行,也就從修改變成了新增操作了。
PUT /{索引庫名}/_doc/文檔id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
PUT /heima/_doc/1
{
"info": "黑馬程式員進階Java講師",
"email": "[email protected]",
"name": {
"firstName": "雲",
"lastName": "趙"
}
}
3.4.2.增量修改
增量修改是隻修改指定id比對的文檔中的部分字段。
POST /{索引庫名}/_update/文檔id
{
"doc": {
"字段名": "新的值",
}
}
POST /heima/_update/1
{
"doc": {
"email": "[email protected]"
}
}
3.5.總結
文檔操作有哪些?
- 建立文檔:POST /{索引庫名}/_doc/文檔id { json文檔 }
- 查詢文檔:GET /{索引庫名}/_doc/文檔id
- 删除文檔:DELETE /{索引庫名}/_doc/文檔id
- 修改文檔:
- 全量修改:PUT /{索引庫名}/_doc/文檔id { json文檔 }
- 增量修改:POST /{索引庫名}/_update/文檔id { "doc": {字段}}
4.RestAPI
ES官方提供了各種不同語言的用戶端,用來操作ES。這些用戶端的本質就是組裝DSL語句,通過http請求發送給ES。官方文檔位址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
其中的Java Rest Client又包括兩種:
- Java Low Level Rest Client
- Java High Level Rest Client
我們學習的是Java HighLevel Rest Client用戶端API
4.0.導入Demo工程
4.0.1.導入資料
首先導入課前資料提供的資料庫資料:
資料結構如下:
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 '經度;例:120.3925',
`pic` varchar(255) DEFAULT NULL COMMENT '酒店圖檔;例:/img/1.jpg',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.0.2.導入項目
然後導入課前資料提供的項目:
項目結構如圖:
4.0.3.mapping映射分析
建立索引庫,最關鍵的是mapping映射,而mapping映射要考慮的資訊包括:
- 字段名
- 字段資料類型
- 是否參與搜尋
- 是否需要分詞
- 如果分詞,分詞器是什麼?
其中:
- 字段名、字段資料類型,可以參考資料表結構的名稱和類型
- 是否參與搜尋要分析業務來判斷,例如圖檔位址,就無需參與搜尋
- 是否分詞呢要看内容,内容如果是一個整體就無需分詞,反之則要分詞
- 分詞器,我們可以統一使用ik_max_word
來看下酒店資料的索引庫結構:
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:地理坐标,裡面包含精度、緯度
- all:一個組合字段,其目的是将多字段的值 利用copy_to合并,提供給使用者搜尋
地理坐标說明:
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)因為SpringBoot預設的ES版本是7.6.2,是以我們需要覆寫預設的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")
));
這裡為了單元測試友善,我們建立一個測試類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.建立索引庫
4.1.1.代碼解讀
建立索引庫的API如下:
代碼分為三步:
- 1)建立Request對象。因為是建立索引庫的操作,是以Request是CreateIndexRequest。
- 2)添加請求參數,其實就是DSL的JSON參數部分。因為json字元串很長,這裡是定義了靜态字元串常量MAPPING_TEMPLATE,讓代碼看起來更加優雅。
- 3)發送請求,client.indices()方法的傳回值是IndicesClient類型,封裝了所有與索引庫操作有關的方法。
4.1.2.完整示例
在hotel-demo的cn.itcast.hotel.constants包下,建立一個類,定義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測試類中,編寫單元測試,實作建立索引:
@Test
void createHotelIndex() throws IOException {
// 1.建立Request對象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.準備請求的參數:DSL語句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
// 3.發送請求
client.indices().create(request, RequestOptions.DEFAULT);
}
4.2.删除索引庫
删除索引庫的DSL語句非常簡單:
DELETE /hotel
與建立索引庫相比:
- 請求方式從PUT變為DELTE
- 請求路徑不變
- 無請求參數
是以代碼的差異,注意展現在Request對象上。依然是三步走:
- 1)建立Request對象。這次是DeleteIndexRequest對象
- 2)準備參數。這裡是無參
- 3)發送請求。改用delete方法
在hotel-demo中的HotelIndexTest測試類中,編寫單元測試,實作删除索引:
@Test
void testDeleteHotelIndex() throws IOException {
// 1.建立Request對象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.發送請求
client.indices().delete(request, RequestOptions.DEFAULT);
}
4.3.判斷索引庫是否存在
判斷索引庫是否存在,本質就是查詢,對應的DSL是:
GET /hotel
是以與删除的Java代碼流程是類似的。依然是三步走:
- 1)建立Request對象。這次是GetIndexRequest對象
- 3)發送請求。改用exists方法
@Test
void testExistsHotelIndex() throws IOException {
// 1.建立Request對象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.發送請求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.輸出
System.err.println(exists ? "索引庫已經存在!" : "索引庫不存在!");
}
4.4.總結
JavaRestClient操作elasticsearch的流程基本類似。核心是client.indices()方法來擷取索引庫的操作對象。
索引庫操作的基本步驟:
- 初始化RestHighLevelClient
- 建立XxxIndexRequest。XXX是Create、Get、Delete
- 準備DSL( Create時需要,其它是無參)
- 發送請求。調用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete
5.RestClient操作文檔
為了與索引庫操作分離,我們再次參加一個測試類,做兩件事情:
- 我們的酒店資料在資料庫,需要利用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.新增文檔
我們要将資料庫的酒店資料查詢出來,寫入elasticsearch中。
5.1.1.索引庫實體類
資料庫查詢後的結果是一個Hotel類型的對象。結構如下:
@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;
}
與我們的索引庫結構存在差異:
- longitude和latitude需要合并為location
是以,我們需要定義一個新的類型,與索引庫結構吻合:
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
}
對應的java代碼如圖:
可以看到與建立索引庫類似,同樣是三步走:
- 1)建立Request對象
- 2)準備請求參數,也就是DSL中的JSON文檔
- 3)發送請求
變化的地方在于,這裡直接使用client.xxx()的API,不再需要client.indices()了。
5.1.3.完整代碼
我們導入酒店資料,基本流程一緻,但是需要考慮幾點變化:
- 酒店資料來自于資料庫,我們需要先查詢出來,得到hotel對象
- hotel對象需要轉為HotelDoc對象
- HotelDoc需要序列化為json格式
是以,代碼整體步驟如下:
- 1)根據id查詢酒店資料Hotel
- 2)将Hotel封裝為HotelDoc
- 3)将HotelDoc序列化為JSON
- 4)建立IndexRequest,指定索引庫名和id
- 5)準備請求參數,也就是JSON文檔
- 6)發送請求
在hotel-demo的HotelDocumentTest測試類中,編寫單元測試:
@Test
void testAddDocument() throws IOException {
// 1.根據id查詢酒店資料
Hotel hotel = hotelService.getById(61083L);
// 2.轉換為文檔類型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 3.将HotelDoc轉json
String json = JSON.toJSONString(hotelDoc);
// 1.準備Request對象
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
// 2.準備Json文檔
request.source(json, XContentType.JSON);
// 3.發送請求
client.index(request, RequestOptions.DEFAULT);
}
5.2.查詢文檔
5.2.1.文法說明
查詢的DSL語句如下:
GET /hotel/_doc/{id}
非常簡單,是以代碼大概分兩步:
- 準備Request對象
- 發送請求
不過查詢的目的是得到結果,解析為HotelDoc,是以難點是結果的解析。完整代碼如下:
可以看到,結果是一個JSON,其中文檔放在一個
_source
屬性中,是以解析就是拿到
_source
,反序列化為Java對象即可。
與之前類似,也是三步走:
- 1)準備Request對象。這次是查詢,是以是GetRequest
- 2)發送請求,得到結果。因為是查詢,這裡調用client.get()方法
- 3)解析結果,就是對JSON做反序列化
5.2.2.完整代碼
@Test
void testGetDocumentById() throws IOException {
// 1.準備Request
GetRequest request = new GetRequest("hotel", "61082");
// 2.發送請求,得到響應
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析響應結果
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代碼應該依然是三步走:
- 1)準備Request對象,因為是删除,這次是DeleteRequest對象。要指定索引庫名和id
- 2)準備參數,無參
- 3)發送請求。因為是删除,是以是client.delete()方法
@Test
void testDeleteDocument() throws IOException {
// 1.準備Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.發送請求
client.delete(request, RequestOptions.DEFAULT);
}
5.4.修改文檔
5.4.1.文法說明
修改我們講過兩種方式:
- 全量修改:本質是先根據id删除,再新增
- 增量修改:修改文檔中的指定字段值
在RestClient的API中,全量修改與新增的API完全一緻,判斷依據是ID:
- 如果新增時,ID已經存在,則修改
- 如果新增時,ID不存在,則新增
這裡不再贅述,我們主要關注增量修改。
代碼示例如圖:
- 1)準備Request對象。這次是修改,是以是UpdateRequest
- 2)準備參數。也就是JSON文檔,裡面包含要修改的字段
- 3)更新文檔。這裡調用client.update()方法
5.4.2.完整代碼
@Test
void testUpdateDocument() throws IOException {
// 1.準備Request
UpdateRequest request = new UpdateRequest("hotel", "61083");
// 2.準備請求參數
request.doc(
"price", "952",
"starName", "四鑽"
);
// 3.發送請求
client.update(request, RequestOptions.DEFAULT);
}
5.5.批量導入文檔
案例需求:利用BulkRequest批量将資料庫資料導入到索引庫中。
步驟如下:
- 利用mybatis-plus查詢酒店資料
- 将查詢到的酒店資料(Hotel)轉換為文檔類型資料(HotelDoc)
- 利用JavaRestClient中的BulkRequest批處理,實作批量新增文檔
5.5.1.文法說明
批量處理BulkRequest,其本質就是将多個普通的CRUD請求組合在一起發送。
其中提供了一個add方法,用來添加其他請求:
可以看到,能添加的請求包括:
- IndexRequest,也就是新增
- UpdateRequest,也就是修改
- DeleteRequest,也就是删除
是以Bulk中添加了多個IndexRequest,就是批量新增功能了。示例:
其實還是三步走:
- 1)建立Request對象。這裡是BulkRequest
- 2)準備參數。批處理的參數,就是其它Request對象,這裡就是多個IndexRequest
- 3)發起請求。這裡是批處理,調用的方法為client.bulk()方法
5.5.2.完整代碼
@Test
void testBulkRequest() throws IOException {
// 批量查詢酒店資料
List<Hotel> hotels = hotelService.list();
// 1.建立Request
BulkRequest request = new BulkRequest();
// 2.準備參數,添加多個新增的Request
for (Hotel hotel : hotels) {
// 2.1.轉換為文檔類型HotelDoc
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2.2.建立新增文檔的Request對象
request.add(new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
// 3.發送請求
client.bulk(request, RequestOptions.DEFAULT);
}
5.6.小結
- 建立XxxRequest。XXX是Index、Get、Update、Delete、Bulk
- 準備參數(Index、Update、Bulk時需要)
- 發送請求。調用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
- 解析結果(Get時需要)