黑馬商城作為一個電商項目,商品的搜索肯定是訪問頻率最高的頁面之一。目前搜索功能是基于數據庫的模糊搜索來實現的,存在很多問題。
首先,查詢效率較低。
由于數據庫模糊查詢不走索引,在數據量較大的時候,查詢性能很差。
需要注意的是,數據庫模糊查詢隨著表數據量的增多,查詢性能的下降會非常明顯,而搜索引擎的性能則不會隨著數據增多而下降太多。目前僅10萬不到的數據量差距就如此明顯,如果數據量達到百萬、千萬、甚至上億級別,這個性能差距會非常夸張。
其次,功能單一,數據庫的模糊搜索匹配條件非常苛刻,例如,當我們需要查詢java基礎課程的時候,如果按照這種查詢方式,我們查詢出來的就只能是(---------java基礎課程--------)
前后匹配,那么如果一個店鋪售賣的名為,(java課程,幫助你打好java基礎)
那么這個店鋪售賣的東西就不會被搜索出來
目前全球的搜索引擎技術排名如下:
Elasticsearch功能有很多
- 1 例如github上的代碼搜索
- 2 商品信息搜索
- 3 解決方案搜搜哦
- 4 附近商鋪,打車搜索
- …
初識Elasticsearch
Elasticsearch是由elastic公司開發的一套搜索引擎技術,它是elastic技術棧中的一部分。完整的技術棧包括:
- Elasticsearch:用于數據存儲、計算和搜索
- Logstash/Beats:用于數據收集
- Kibana:用于數據可視化
安裝Elasticsearch
我們依舊采用的是docker安裝,簡單方便
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 hm-net \-p 9200:9200 \-p 9300:9300 \elasticsearch:7.12.1
安裝完成之后訪問9200
端口可以看到基本信息
安裝Kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601 \
kibana:7.12.1
安裝完成之后訪問5601
端口,可以看到控制臺頁面
倒排索引
elasticsearch之所以有如此高性能的搜索表現,正是得益于底層的倒排索引技術。我們首先來看看什么是正向索引。
正向索引
例如我們在mysql當中有一張goods的表
id | title | price |
---|---|---|
1 | 小米手機 | 3499 |
2 | 華為手機 | 4999 |
3 | 華為小米充電器 | 49 |
4 | 小米手環 | 49 |
… | … | … |
其中的id字段已經創建了索引,由于索引底層采用了B+樹結構,因此我們根據id搜索的速度會非常快。但是其他字段例如title,只在葉子節點上存在。
因此要根據title搜索的時候只能遍歷樹中的每一個葉子節點,判斷title數據是否符合要求。
select * from tb_goods where title like '%手機%';
流程如下
說明:
- 1)檢查到搜索條件為like ‘%手機%’,需要找到title中包含手機的數據
- 2)逐條遍歷每行數據(每個葉子節點),比如第1次拿到id為1的數據
- 3)判斷數據中的title字段值是否符合條件
- 4)如果符合則放入結果集,不符合則丟棄
- 5)回到步驟1
綜上所述,如果我們根據有索引的id進行查詢,那么查詢效率就會非常高,但是如果搜索條件為模糊匹配時,由于索引無法生效,導致從索引查詢退化為全表掃描,效率很差。
因此,正向索引適合于根據索引字段的精確搜索,不適合基于部分詞條的模糊匹配。
而倒排索引恰好解決的就是根據部分詞條模糊匹配的問題。
倒排索引
倒排索引中有兩個非常重要的概念:
- 文檔(Document):用來搜索的數據,其中的每一條數據就是一個文檔。例如一個網頁、一個商品信息
- 詞條(Term):對文檔數據或用戶搜索數據,利用某種算法分詞,得到的具備含義的詞語就是詞條。例如:我是中國人,就可以分為:我、是、中國人、中國、國人這樣的幾個詞條
倒排索引的搜索流程如下(以搜索"華為手機"為例),如圖:
流程描述:
1)用戶輸入條件"華為手機"進行搜索。
2)對用戶輸入條件分詞,得到詞條:華為、手機。
3)拿著詞條在倒排索引中查找(由于詞條有索引,查詢效率很高),即可得到包含詞條的文檔id:1、2、3。
4)拿著文檔id到正向索引中查找具體文檔即可(由于id也有索引,查詢效率也很高)。
雖然要先查詢倒排索引,再查詢倒排索引,但是無論是詞條、還是文檔id都建立了索引,查詢速度非常快!無需全表掃描。
基礎概念
elasticsearch是面向文檔(Document)存儲的,可以是數據庫中的一條商品數據,一個訂單信息。文檔數據會被序列化為json格式后存儲在elasticsearch中:
在mysq當中數據存儲方式如下:
但是在es當中如下
{"id": 1,"title": "小米手機","price": 3499
}
{"id": 2,"title": "華為手機","price": 4999
}
{"id": 3,"title": "華為小米充電器","price": 49
}
{"id": 4,"title": "小米手環","price": 299
}
因此,原本數據庫中的一行數據就是ES中的一個JSON文檔;而數據庫中每行數據都包含很多列,這些列就轉換為JSON文檔中的字段(Field)。
索引和映射
隨著業務發展,需要在es中存儲的文檔也會越來越多,比如有商品的文檔、用戶的文檔、訂單文檔等等:
所有文檔都散亂存放顯然非常混亂,也不方便管理。
因此,我們要將類型相同的文檔集中在一起管理,稱為索引(Index)。例如:
商品索引:
{"id": 1,"title": "小米手機","price": 3499
}{"id": 2,"title": "華為手機","price": 4999
}{"id": 3,"title": "三星手機","price": 3999
}
用戶索引
{"id": 101,"name": "張三","age": 21
}{"id": 102,"name": "李四","age": 24
}{"id": 103,"name": "麻子","age": 18
}
Mysql與Es
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實現
- 兩者再基于某種方式,實現數據的同步,保證一致性
IK分詞器
愛坤分詞器(露出雞腳了),es關鍵的就是倒排索引,而倒排索引依賴對文檔內部的詞進行分詞,而分詞則需要高效、精準的分詞算法,IK分詞器就是這樣一個中文分詞算法。
安裝IK分詞器
同樣是docker
方案1:在線安裝
docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
安裝完成之后重啟es
docker restart es
方案2:離線安裝
首先,查看之前安裝的Elasticsearch容器的plugins數據卷目錄:
docker volume inspect es-plugins
[{"CreatedAt": "2024-11-06T10:06:34+08:00","Driver": "local","Labels": null,"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data","Name": "es-plugins","Options": null,"Scope": "local"}
]
可以看到elasticsearch的插件掛載到了/var/lib/docker/volumes/es-plugins/_data這個目錄。我們需要把IK分詞器上傳至這個目錄。
上傳完成之后重啟es容器
簡單使用
IK分詞器包含兩種模式:
- ik_smart:智能語義切分
- ik_max_word:最細粒度切分
首先我們來看看官方提供的標準分詞方式
POST /_analyze
{"analyzer": "standard","text": "黑馬程序員學習java太棒了"
}
{"tokens" : [{"token" : "黑","start_offset" : 0,"end_offset" : 1,"type" : "<IDEOGRAPHIC>","position" : 0},{"token" : "馬","start_offset" : 1,"end_offset" : 2,"type" : "<IDEOGRAPHIC>","position" : 1},{"token" : "程","start_offset" : 2,"end_offset" : 3,"type" : "<IDEOGRAPHIC>","position" : 2},{"token" : "序","start_offset" : 3,"end_offset" : 4,"type" : "<IDEOGRAPHIC>","position" : 3},{"token" : "員","start_offset" : 4,"end_offset" : 5,"type" : "<IDEOGRAPHIC>","position" : 4},{"token" : "學","start_offset" : 5,"end_offset" : 6,"type" : "<IDEOGRAPHIC>","position" : 5},{"token" : "習","start_offset" : 6,"end_offset" : 7,"type" : "<IDEOGRAPHIC>","position" : 6},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "<ALPHANUM>","position" : 7},{"token" : "太","start_offset" : 11,"end_offset" : 12,"type" : "<IDEOGRAPHIC>","position" : 8},{"token" : "棒","start_offset" : 12,"end_offset" : 13,"type" : "<IDEOGRAPHIC>","position" : 9},{"token" : "了","start_offset" : 13,"end_offset" : 14,"type" : "<IDEOGRAPHIC>","position" : 10}]
}
我們可以看到,他對中文的分詞能力很差,一個字分一下,而對英文的java的分詞效果很好。
我們再測試IK分詞器:
POST /_analyze
{"analyzer": "ik_smart","text": "黑馬程序員學習java太棒了"
}
{"tokens" : [{"token" : "黑馬","start_offset" : 0,"end_offset" : 2,"type" : "CN_WORD","position" : 0},{"token" : "程序員","start_offset" : 2,"end_offset" : 5,"type" : "CN_WORD","position" : 1},{"token" : "學習","start_offset" : 5,"end_offset" : 7,"type" : "CN_WORD","position" : 2},{"token" : "java","start_offset" : 7,"end_offset" : 11,"type" : "ENGLISH","position" : 3},{"token" : "太棒了","start_offset" : 11,"end_offset" : 14,"type" : "CN_WORD","position" : 4}]
}
拓展詞典
隨著互聯網的發展,“造詞運動”也越發的頻繁。出現了很多新的詞語,在原有的詞匯列表中并不存在。比如:“泰褲辣”,“傳智播客” 等。而IK分詞器無法對這些詞匯分詞。
所以要想正確分詞,IK分詞器的詞庫也需要不斷的更新,IK分詞器提供了擴展詞匯的功能。
1)打開IK分詞器config目錄:
2)在IKAnalyzer.cfg.xml配置文件內容添加:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties><comment>IK Analyzer 擴展配置</comment><!--用戶可以在這里配置自己的擴展字典 *** 添加擴展詞典--><entry key="ext_dict">ext.dic</entry>
</properties>
3)在IK分詞器的config目錄新建一個 ext.dic,可以參考config目錄下復制一個配置文件進行修改
傳智播客
泰褲辣
4)重啟elasticsearch
此外我們還可以添加斷分詞的字段,同樣是在IKAnalyzer.cfg.xml里配置,注意在線安裝的話沒有config目錄,但是也不要手動創建config目錄,具體解決方法可以在網上搜索。
索引庫操作
Mapping映射屬性
Mapping是對索引庫中文檔的約束,常見的Mapping屬性包括:
- type:字段數據類型,常見的簡單類型有:
- 字符串:text(可分詞的文本)、keyword(精確值,例如:品牌、國家、ip地址)
- 數值:long、integer、short、byte、double、float、
- 布爾:boolean
- 日期:date
- 對象:object
- index:是否創建索引,默認為true
- analyzer:使用哪種分詞器
- properties:該字段的子字段
索引庫的CRUD
創建索引庫與映射
基本語法:
- 請求方式:
PUT
- 請求路徑:/索引庫名,可以自定義
- 請求參數:mapping映射
PUT /索引庫名稱
{"mappings": {"properties": {"字段名":{"type": "text","analyzer": "ik_smart"},"字段名2":{"type": "keyword","index": "false"},"字段名3":{"properties": {"子字段": {"type": "keyword"}}},// ...略}}
}
例子
## 創建索引庫并且設置mapping映射
PUT /heima
{"mappings": {"properties": {"info":{"type": "text","analyzer": "ik_smart" // 分詞其類型},"age":{"type": "byte"},"email":{"type": "keyword","index": false // 不創建索引},"name":{"type": "object","properties": {"fistName":{"type": "keyword" },"lastName":{"type": "keyword"}}}}}
}
查詢索引庫
基本語法:
- 請求方式:GET
- 請求路徑:/索引庫名
- 請求參數:無
GET /索引庫名
修改索引庫
倒排索引結構雖然不復雜,但是一旦數據結構改變(比如改變了分詞器),就需要重新創建倒排索引,這簡直是災難。因此索引庫一旦創建,無法修改mapping。
雖然無法修改mapping中已有的字段,但是卻允許添加新的字段到mapping中,因為不會對倒排索引產生影響。因此修改索引庫能做的就是向索引庫中添加新字段
,或者更新索引庫的基礎屬性
。
PUT /索引庫名/_mapping
{"properties": {"新字段名":{"type": "integer"}}
}
刪除索引庫
語法:
- 請求方式:DELETE
- 請求路徑:/索引庫名
- 請求參數:無
DELETE /索引庫名
文檔操作
新增文檔
POST /索引庫名/_doc/文檔id
{"字段1": "值1","字段2": "值2","字段3": {"子屬性1": "值3","子屬性2": "值4"},
}
查詢文檔
根據rest風格,新增是post,查詢應該是get,不過查詢一般都需要條件,這里我們把文檔id帶上。
GET /{索引庫名稱}/_doc/{id}
刪除文檔
就不用多說了肯定是delete
DELETE /{索引庫名}/_doc/id值
修改文檔
修改文檔由兩種方式:
- 全量修改:直接覆蓋原來的文檔
- 局部修改:根據提供的字段進行修改
全量修改
全量修改的方法和新增文檔的方法一樣
全量修改是覆蓋原來的文檔,其本質是兩步操作:
- 根據指定的id刪除文檔
- 新增一個相同id的文檔
PUT /{索引庫名}/_doc/文檔id
{"字段1": "值1","字段2": "值2",// ... 略
}
這時候如果寫了一個不存在的id那么就是新增
局部修改
局部修改是只修改指定id匹配的文檔中的部分字段。
POST /{索引庫名}/_update/文檔id
{"doc": {"字段名": "新的值",}
}
批處理
批量處理的代碼如下
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
RestAPI
在elasticsearch提供的API中,與elasticsearch一切交互都封裝在一個名為RestHighLevelClient的類中,必須先完成這個對象的初始化,建立與elasticsearch的連接。
初始化連接
分為三步:
1)在item-service模塊中引入es的RestHighLevelClient依賴:
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2)因為SpringBoot默認的ES版本是7.17.10,所以我們需要覆蓋默認的ES版本:
<properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><elasticsearch.version>7.12.1</elasticsearch.version>
</properties>
3)初始化RestHighLevelClient:
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")
));
@Configuration
public class ElasticClient {@Bean(destroyMethod = "close")@ConditionalOnMissingBeanpublic RestHighLevelClient restHighLevelClient() {return new RestHighLevelClient(RestClient.builder(new HttpHost("192.168.200.200",9200,"http")));}}
創建索引庫
Mapping映射
最終我們需要改造黑馬商城的搜索業務,經過我們分析,原本的增加代碼如下
PUT /items
{"mappings": {"properties": {"id": {"type": "keyword"},"name":{"type": "text","analyzer": "ik_max_word"},"price":{"type": "integer"},"stock":{"type": "integer"},"image":{"type": "keyword","index": false},"category":{"type": "keyword"},"brand":{"type": "keyword"},"sold":{"type": "integer"},"commentCount":{"type": "integer","index": false},"isAD":{"type": "boolean"},"updateTime":{"type": "date"}}}
}
創建索引
創建索引的API如下
代碼分為三步:
- 1)創建Request對象。
- 因為是創建索引庫的操作,因此Request是CreateIndexRequest。
- 2)添加請求參數
- 其實就是Json格式的Mapping映射參數。因為json字符串很長,這里是定義了靜態字符串常量MAPPING_TEMPLATE,讓代碼看起來更加優雅。
- 3)發送請求
- client.indices()方法的返回值是IndicesClient類型,封裝了所有與索引庫操作有關的方法。例如創建索引、刪除索引、判斷索引是否存在等
@Test
void testCreateIndex() throws IOException {// 1.創建Request對象CreateIndexRequest request = new CreateIndexRequest("items");// 2.準備請求參數request.source(MAPPING_TEMPLATE, XContentType.JSON);// 3.發送請求client.indices().create(request, RequestOptions.DEFAULT);
}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" +" },\n" +" \"price\":{\n" +" \"type\": \"integer\"\n" +" },\n" +" \"stock\":{\n" +" \"type\": \"integer\"\n" +" },\n" +" \"image\":{\n" +" \"type\": \"keyword\",\n" +" \"index\": false\n" +" },\n" +" \"category\":{\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"brand\":{\n" +" \"type\": \"keyword\"\n" +" },\n" +" \"sold\":{\n" +" \"type\": \"integer\"\n" +" },\n" +" \"commentCount\":{\n" +" \"type\": \"integer\"\n" +" },\n" +" \"isAD\":{\n" +" \"type\": \"boolean\"\n" +" },\n" +" \"updateTime\":{\n" +" \"type\": \"date\"\n" +" }\n" +" }\n" +" }\n" +"}";
刪除索引庫
不用多說對象為DeleteIndexRequest
@Test
void testDeleteIndex() throws IOException {// 1.創建Request對象DeleteIndexRequest request = new DeleteIndexRequest("items");// 2.發送請求client.indices().delete(request, RequestOptions.DEFAULT);
}
判斷索引庫是否存在
不用多說對象為GetIndexRequest
@Test
void testExistsIndex() throws IOException {// 1.創建Request對象GetIndexRequest request = new GetIndexRequest("items");// 2.發送請求boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);// 3.輸出System.err.println(exists ? "索引庫已經存在!" : "索引庫不存在!");
}
RestClient文檔操作
新增文檔
新增文檔的API如下
POST /{索引庫名}/_doc/1
{"name": "Jack","age": 21
}
可以看到與索引庫操作的API非常類似,同樣是三步走:
- 1)創建Request對象,這里是IndexRequest,因為添加文檔就是創建倒排索引的過程
- 2)準備請求參數,本例中就是Json文檔
- 3)發送請求
變化的地方在于,這里直接使用client.xxx()的API,不再需要client.indices()了。
@Test
void testAddDocument() throws IOException {// 1.根據id查詢商品數據Item item = itemService.getById(100002644680L);// 2.轉換為文檔類型ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);// 3.將ItemDTO轉jsonString doc = JSONUtil.toJsonStr(itemDoc);// 1.準備Request對象IndexRequest request = new IndexRequest("items").id(itemDoc.getId());// 2.準備Json文檔request.source(doc, XContentType.JSON);// 3.發送請求client.index(request, RequestOptions.DEFAULT);
}
查詢文檔
查詢的請求語句如下:
GET /{索引庫名}/_doc/{id}
與之前的流程類似,代碼大概分2步:
- 創建Request對象
- 準備請求參數,這里是無參,直接省略
- 發送請求
可以看到,響應結果是一個JSON,其中文檔放在一個_source屬性中,因此解析就是拿到_source,反序列化為Java對象即可。
其它代碼與之前類似,流程如下:
- 1)準備Request對象。這次是查詢,所以是GetRequest
- 2)發送請求,得到結果。因為是查詢,這里調用client.get()方法
- 3)解析結果,就是對JSON做反序列化
@Test
void testGetDocumentById() throws IOException {// 1.準備Request對象GetRequest request = new GetRequest("items").id("100002644680");// 2.發送請求GetResponse response = client.get(request, RequestOptions.DEFAULT);// 3.獲取響應結果中的sourceString json = response.getSourceAsString();ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);System.out.println("itemDoc= " + ItemDoc);
}
刪除文檔
刪除的請求語句如下:
DELETE /hotel/_doc/{id}
與查詢相比,僅僅是請求方式從DELETE變成GET,可以想象Java代碼應該依然是2步走:
- 1)準備Request對象,因為是刪除,這次是DeleteRequest對象。要指定索引庫名和id
- 2)準備參數,無參,直接省略
- 3)發送請求。因為是刪除,所以是client.delete()方法
@Test
void testDeleteDocument() throws IOException {// 1.準備Request,兩個參數,第一個是索引庫名,第二個是文檔idDeleteRequest request = new DeleteRequest("item", "100002644680");// 2.發送請求client.delete(request, RequestOptions.DEFAULT);
}
修改文檔
我們上面說修改文檔由兩種方法,但是第一種方法我們肯定是不推薦的,因此這里直接看第二種方法
POST /{索引庫名}/_update/{id}
{"doc": {"字段名": "字段值","字段名": "字段值"}
}
@Test
void testUpdateDocument() throws IOException {// 1.準備RequestUpdateRequest request = new UpdateRequest("items", "100002644680");// 2.準備請求參數request.doc("price", 58800,"commentCount", 1);// 3.發送請求client.update(request, RequestOptions.DEFAULT);
}
批量導入文檔
在之前的案例中,我們都是操作單個文檔。而數據庫中的商品數據實際會達到數十萬條,某些項目中可能達到數百萬條。
我們如果要將這些數據導入索引庫,肯定不能逐條導入,而是采用批處理方案。常見的方案有:
- 利用Logstash批量導入
- 需要安裝Logstash
- 對數據的再加工能力較弱
- 無需編碼,但要學習編寫Logstash導入配置
- 利用JavaAPI批量導入
- 需要編碼,但基于JavaAPI,學習成本低
- 更加靈活,可以任意對數據做再加工處理后寫入索引庫
語法說明
批處理與前面講的文檔的CRUD步驟基本一致:
- 創建Request,但這次用的是BulkRequest
- 準備請求參數
- 發送請求,這次要用到client.bulk()方法
BulkRequest本身其實并沒有請求參數,其本質就是將多個普通的CRUD請求組合在一起發送。例如:
- 批量新增文檔,就是給每個文檔創建一個IndexRequest請求,然后封裝到BulkRequest中,一起發出。
- 批量刪除,就是創建N個DeleteRequest請求,然后封裝到BulkRequest,一起發出
可以看到,能添加的請求有:
- IndexRequest,也就是新增
- UpdateRequest,也就是修改
- DeleteRequest,也就是刪除
示例
@Test
void testBulk() throws IOException {// 1.創建RequestBulkRequest request = new BulkRequest();// 2.準備請求參數request.add(new IndexRequest("items").id("1").source("json doc1", XContentType.JSON));request.add(new IndexRequest("items").id("2").source("json doc2", XContentType.JSON));// 3.發送請求client.bulk(request, RequestOptions.DEFAULT);
}
完整代碼
@Test
void testLoadItemDocs() throws IOException {// 分頁查詢商品數據int pageNo = 1;int size = 1000;while (true) {Page<Item> page = itemService.lambdaQuery().eq(Item::getStatus, 1).page(new Page<Item>(pageNo, size));// 非空校驗List<Item> items = page.getRecords();if (CollUtils.isEmpty(items)) {return;}log.info("加載第{}頁數據,共{}條", pageNo, items.size());// 1.創建RequestBulkRequest request = new BulkRequest("items");// 2.準備參數,添加多個新增的Requestfor (Item item : items) {// 2.1.轉換為文檔類型ItemDTOItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);// 2.2.創建新增文檔的Request對象request.add(new IndexRequest().id(itemDoc.getId()).source(JSONUtil.toJsonStr(itemDoc), XContentType.JSON));}// 3.發送請求client.bulk(request, RequestOptions.DEFAULT);// 翻頁pageNo++;}
}