前言
簡(jiǎn)介
在 Elasticsearch7.15版本之后,Elasticsearch官方將它的高級(jí)客戶端 RestHighLevelClient標(biāo)記為棄用狀態(tài)。同時(shí)推出了全新的 Java API客戶端 Elasticsearch Java API Client,該客戶端也將在 Elasticsearch8.0及以后版本中成為官方推薦使用的客戶端。
Elasticsearch Java API Client 支持除 Vector tile search API 和 Find structure API 之外的所有 Elasticsearch API。且支持所有API數(shù)據(jù)類型,并且不再有原始JsonValue屬性。它是針對(duì)Elasticsearch8.0及之后版本的客戶端,所以我們需要學(xué)習(xí)新的Elasticsearch Java API Client的使用方法。
為什么要拋棄High Level Rest:
-
客戶端"too heavy",相關(guān)依賴超過 30 MB,且很多都是非必要相關(guān)的;api 暴露了很多服務(wù)器內(nèi)部接口
-
一致性差,仍需要大量的維護(hù)工作。
-
客戶端沒有集成 json/object 類型映射,仍需要自己借助字節(jié)緩存區(qū)實(shí)現(xiàn)。
Java API Client最明顯的特征:
- 支持lambda表達(dá)式操作ES
- 支持Builder建造者模式操作ES,鏈?zhǔn)酱a具有較強(qiáng)可讀性.
- 應(yīng)用程序類能夠自動(dòng)映射為Mapping.
- 所有Elasticsearch API的強(qiáng)類型請(qǐng)求和響應(yīng)。
- 所有API的阻塞和異步版本
- 將協(xié)議處理委托給http客戶端(如Java低級(jí)REST客戶端),該客戶端負(fù)責(zé)處理所有傳輸級(jí)問題:HTTP連接池、重試、節(jié)點(diǎn)發(fā)現(xiàn)等。
官方地址
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/7.17/indexing.html
簡(jiǎn)單使用
1:導(dǎo)包
這里記住你的elasticsearch-java必須對(duì)應(yīng)你電腦上裝的ES版本
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>7.17.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
2:開啟鏈接
//創(chuàng)建一個(gè)低級(jí)的客戶端
final RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();
//創(chuàng)建JSON對(duì)象映射器
final RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
//創(chuàng)建API客戶端
final ElasticsearchClient client = new ElasticsearchClient(transport);
3:關(guān)閉鏈接
client.shutdown();
transport.close();
restClient.close();
4:完整代碼
public class Client {
public static void main(String[] args) throws IOException {
//創(chuàng)建一個(gè)低級(jí)的客戶端
final RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();
//創(chuàng)建JSON對(duì)象映射器
final RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
//創(chuàng)建API客戶端
final ElasticsearchClient client = new ElasticsearchClient(transport);
//查詢所有索引-------------------------------------------------------------------------------------
final GetIndexResponse response = client.indices().get(query -> query.index("_all"));
final IndexState products = response.result().get("products");
System.out.println(products.toString());
//關(guān)閉
client.shutdown();
transport.close();
restClient.close();
}
}
JsonData類
原始JSON值??梢允褂肑sonpMapper將其轉(zhuǎn)換為JSON節(jié)點(diǎn)樹或任意對(duì)象。 此類型在API類型中用于沒有靜態(tài)定義類型或無法表示為封閉數(shù)據(jù)結(jié)構(gòu)的泛型參數(shù)的值。 API客戶端返回的此類實(shí)例保留對(duì)客戶端的JsonpMapper的引用,并且可以使用to(class)轉(zhuǎn)換為任意類型,而不需要顯式映射器
我們一般在ES的DSL范圍查詢中會(huì)使用到!
核心方法:
- to:將此對(duì)象轉(zhuǎn)換為目標(biāo)類。必須在創(chuàng)建時(shí)提供映射器
- from:從讀取器創(chuàng)建原始JSON值
- of:從現(xiàn)有對(duì)象創(chuàng)建原始JSON值,以及用于進(jìn)一步轉(zhuǎn)換的映射器
- deserialize:使用反序列化程序轉(zhuǎn)換此對(duì)象。必須在創(chuàng)建時(shí)提供映射器
高階使用
1:ES配置類
// 配置的前綴
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
public class ESClientConfig {
/**
* 多個(gè)IP逗號(hào)隔開
*/
@Setter
private String hosts;
/**
* 同步方式
*
* @return
*/
@Bean
public ElasticsearchClient elasticsearchClient() {
HttpHost[] httpHosts = toHttpHost();
// Create the RestClient
//RestClient restClient = RestClient.builder(httpHosts).build();
RestClient restClient = RestClient.builder(httpHosts)
.setHttpClientConfigCallback(httpClientBuilder
->httpClientBuilder.setDefaultHeaders(
listOf(new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.toString())))
.addInterceptorLast((HttpResponseInterceptor) (response, context)
-> response.addHeader("X-Elastic-Product", "Elasticsearch"))).build();
// Create the transport with a Jackson mapper
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
// create the API client
return new ElasticsearchClient(transport);
}
/**
* 異步方式
*
* @return
*/
@Bean
public ElasticsearchAsyncClient elasticsearchAsyncClient() {
HttpHost[] httpHosts = toHttpHost();
RestClient restClient = RestClient.builder(httpHosts).build();
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchAsyncClient(transport);
}
/**
* 解析配置的字符串hosts,轉(zhuǎn)為HttpHost對(duì)象數(shù)組
*
* @return
*/
private HttpHost[] toHttpHost() {
if (!StringUtils.hasLength(hosts)) {
throw new RuntimeException("invalid elasticsearch configuration. elasticsearch.hosts不能為空!");
}
// 多個(gè)IP逗號(hào)隔開
String[] hostArray = hosts.split(",");
HttpHost[] httpHosts = new HttpHost[hostArray.length];
HttpHost httpHost;
for (int i = 0; i < hostArray.length; i++) {
String[] strings = hostArray[i].split(":");
httpHost = new HttpHost(strings[0], Integer.parseInt(strings[1]), "http");
httpHosts[i] = httpHost;
}
return httpHosts;
}
}
2:查詢所有索引
//省略連接...
final GetIndexResponse all = client.indices().get(query -> query.index("_all"));
System.out.println(all.toString());
//省略關(guān)閉...
3:查詢某個(gè)索引
//查詢某個(gè)索引
final GetIndexResponse products = client.indices().get(query -> query.index("products"));
System.err.println(products.toString());
4:創(chuàng)建索引
//查詢某個(gè)索引是否存在
boolean exists = client.indices().exists(query -> query.index("products")).value();
System.out.println(exists);
if (exists) {
System.err.println("索引已存在");
} else {
final CreateIndexResponse products = client.indices().create(builder -> builder.index("products"));
System.err.println(products.acknowledged());
}
5:刪除指定索引
//刪除指定索引
boolean exists = client.indices().exists(query -> query.index("products")).value();
System.out.println(exists);
if (exists) {
DeleteIndexResponse response = client.indices().delete(query -> query.index("products"));
System.err.println(response.acknowledged());
} else {
System.err.println("索引不存在");
}
6:查詢索引的映射
//查詢映射信息
final GetIndexResponse response = client.indices().get(builder -> builder.index("produces"));
System.err.println(response.result().get("produces").mappings());
7:創(chuàng)建索引指定映射
numberOfReplicas(“1”):設(shè)置副本
numberOfShards(“1”):設(shè)置分片
//創(chuàng)建索引指定映射,分片和副本信息
final CreateIndexResponse response = client.indices().create(builder ->
builder.settings(indexSetting -> indexSetting.numberOfReplicas("1").numberOfShards("1")).mappings(
map -> map
.properties("name", propertyBuilder -> propertyBuilder.keyword(keywordProperty -> keywordProperty))
.properties("price", propertyBuilder -> propertyBuilder.double_(doubleNumProperty -> doubleNumProperty))
.properties("des", propertyBuilder -> propertyBuilder.text(textProperty -> textProperty.analyzer("ik_smart").searchAnalyzer("ik_smart")))
).index("produces")
);
8:創(chuàng)建文檔
使用HashMap作為數(shù)據(jù)存儲(chǔ)容器
//創(chuàng)建文檔
//1.創(chuàng)建HashMap進(jìn)行存儲(chǔ)數(shù)據(jù),文檔要對(duì)應(yīng)映射
final HashMap<String, Object> doc = new HashMap<>();
doc.put("name","辣條");
doc.put("age",12);
doc.put("id","11111");
//2.將文檔存入索引中
final IndexResponse response = client.index(builder -> builder.index("produces").id(doc.get("id")).document(doc));
System.err.println(response.version());
使用自定義類作為數(shù)據(jù)存儲(chǔ)容器
實(shí)體類
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Produce {
private String id;
private String name;
private double age;
}
//創(chuàng)建文檔
final Produce produce = new Produce("123", "小明", 18);
final IndexResponse response = client.index(builder -> builder.index("produces").id(produce.getId()).document(produce));
System.err.println(response.version());
使用外部JSON數(shù)據(jù)創(chuàng)建
這里要注意我們需要使用StringReader進(jìn)行讀取時(shí)使用replace函數(shù)將設(shè)置的’改為",當(dāng)然這在真實(shí)的業(yè)務(wù)中肯定不會(huì)有,因?yàn)檎鎸?shí)業(yè)務(wù)中一定是標(biāo)準(zhǔn)的JSON數(shù)據(jù),無需使用replace進(jìn)行替換了
//創(chuàng)建文檔
final StringReader input = new StringReader(
"{'name':'農(nóng)夫三拳','price':3.00,'des':'農(nóng)夫三拳有點(diǎn)甜'}".replace('\'', '"')
);
final IndexResponse response = client.index(builder -> builder.index("produces").id("44514").withJson(input));
System.err.println(response.version());
9: 查詢所有文檔
final SearchResponse<Object> response = client.search(builder -> builder.index("produces"), Object.class);
final List<Hit<Object>> hits = response.hits().hits();
hits.forEach(
x-> System.err.println(x)
);
10:根據(jù)ID查詢文檔
使用HashMap對(duì)應(yīng)查詢
//查詢文檔
final GetResponse<Map> response = client.get(builder -> builder.index("produces").id("116677"), Map.class);
final Map source = response.source();
source.forEach((x,y)->{
System.err.println(x+":"+y);
});
使用自定義類對(duì)應(yīng)查詢
final GetResponse<Produce> response1 = client.get(builder -> builder.index("produces").id("aabbcc123"), Produce.class);
final Produce source1 = response1.source();
System.err.println(source1.toString());
11:刪除文檔
final GetResponse<Produce> response1 = client.get(builder -> builder.index("produces").id("aabbcc123"), Produce.class);
final Produce source1 = response1.source();
System.err.println(source1.toString());
12:修改文檔
全覆蓋
//修改文檔(覆蓋)
final Produce produce = new Produce("ccaabb123", "旺仔搖滾洞", "旺仔搖滾洞亂搖亂滾", 10.23D);
final UpdateResponse<Produce> response = client.update(builder -> builder.index("produces").id("aabbcc123").doc(produce), Produce.class);
System.err.println(response.shards().successful());
修改部分文檔
區(qū)別在于我們需要設(shè)置.docAsUpsert(true)表明是修改部分而不是覆蓋
//修改文檔(部分修改)
// final Produce produce = new Produce("ccaabb123", "旺仔搖滾洞", "旺仔搖滾洞亂搖亂滾", 10.23D);
final Produce produce = new Produce();
produce.setName("旺仔搖不動(dòng)");
final UpdateResponse<Produce> response = client.update(builder -> builder.index("produces").id("aabbcc123").doc(produce).docAsUpsert(true), Produce.class);
System.err.println(response.shards().successful());
13:批量操作
批量新增
produceList.add(produce1);
produceList.add(produce2);
produceList.add(produce3);
//構(gòu)建BulkRequest
final BulkRequest.Builder br = new BulkRequest.Builder();
for (Produce produce : produceList) {
br.operations(op->op.index(idx->idx.index("produces").id(produce.getSku()).document(produce)));
}
final BulkResponse response = client.bulk(br.build());
批量刪除
List<BulkOperation> bulkOperations = new ArrayList<>();
// 向集合中添加需要?jiǎng)h除的文檔id信息
for (int i = 0; i < dto.getIds().size(); i++) {
int finalI = i;
bulkOperations.add(BulkOperation.of(b -> b
.delete((d -> d
.index(dto.getIndex())
.id(dto.getIds().get(finalI))
))
));
}
// 調(diào)用客戶端的bulk方法,并獲取批量操作響應(yīng)結(jié)果
BulkResponse response = client
.bulk(e -> e
.index(dto.getIndex())
.operations(bulkOperations));
批量更新
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", deleteIds);
jsonObject.put("status", 1);
BulkRequest.Builder br = new BulkRequest.Builder();
for (String deleteId : deleteIds) {
br.operations(op -> op
.update(idx ->
idx.index(EsIndexConstants.opinion_information)
.id(deleteId)
.action(a -> a
.doc(jsonObject)//局部修改
.docAsUpsert(true)//局部修改
)
)).refresh(Refresh.True);
}
BulkRequest bulkRequest = br.build();
BulkResponse result = null;
try {
result = elasticsearchClient.bulk(bulkRequest);
} catch (IOException e) {
throw new RuntimeException(e);
}
DSL查詢
1:matchAll查詢所有文檔
//matchAll
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q ->
q.matchAll(
v->v
)), Produce.class);
System.err.println(response.hits().hits());
2:match 根據(jù)字段查詢
//簡(jiǎn)單query方式查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q ->
q.match(t ->
t.field("name")
.query("龍虎萬精油"))), Produce.class);
System.err.println(response.hits().hits());
3:多id查詢
//多ID查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q ->
q.ids(sid->sid.values("1000","1001"))), Produce.class);
System.err.println(response.hits().hits());
4:term 不分詞查詢
//term不分詞條件查詢
final SearchResponse<Produce> response = client.search(builder -> builder.index("produces")
.query(q -> q.term(t -> t.field("name").value("風(fēng)油精"))), Produce.class);
System.err.println(response.hits().hits());
5:范圍查詢
//范圍查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->
q.range(r ->
r.field("price").gt(JsonData.of(5D)).lt(JsonData.of(15D)))),
Produce.class);
System.err.println(response.hits().hits());
6: 前綴查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->q.prefix(p->p.field("name").value("六"))),
Produce.class);
System.err.println(response.hits().hits());
7:匹配查詢
//匹配查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->q.wildcard(w->w.field("name").value("風(fēng)*"))),
Produce.class);
System.err.println(response.hits().hits());
?單字符匹配
//匹配查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->q.wildcard(w->w.field("name").value("風(fēng)?精"))),
Produce.class);
System.err.println(response.hits().hits());
8:模糊查詢
//模糊查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->q.fuzzy(f->f.field("name").value("六仙花露水"))),
Produce.class);
System.err.println(response.hits().hits());
9:多條件查詢
使用bool關(guān)鍵字配合must,should,must_not
- must:所有條件必須同時(shí)成立
- must_not:所有條件必須同時(shí)不成立
- should:所有條件中成立一個(gè)即可
//多條件
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q ->
q.bool(b ->
b.must(t ->
t.term(v ->
v.field("name")
.value("旺仔搖不動(dòng)")))
.must(t2 ->
t2.term(v2 ->
v2.field("price")
.value(0.0D))))),
Produce.class);
System.err.println(response.hits().hits());
或者創(chuàng)建BoolQuery.Builder,以便進(jìn)行業(yè)務(wù)判斷是否增加查詢條件
List<FieldValue> fieldValues = new ArrayList<>();
fieldValues.add(FieldValue.of(10));
fieldValues.add(FieldValue.of(100));
BoolQuery.Builder boolQuery = new BoolQuery.Builder();
boolQuery.must(t->
t.terms(v->
v.field("label")
.terms(term->
term.value(fieldValues))));
boolQuery.must(t->
t.match(f->
f.field("name")
.query("旺仔")));
SearchResponse<Object> search = elasticsearchClient.search(builder -> builder.index("my_test_index")
.query(q->
q.bool(boolQuery.build())),Object.class);
10:多字段查詢-multiMatch
//多字段查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces").query(q->q.multiMatch(qs->qs.query("蚊蟲叮咬 辣眼睛").fields("name","des"))),
Produce.class);
System.err.println(response.hits().hits());
11:高亮顯示
我們注意要設(shè)置前綴和后綴
//高亮顯示
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q -> q.match(v -> v.field("name").query("風(fēng)油精")))
.highlight(h -> h.preTags("<span>").postTags("<span>").fields("name", hf -> hf)),
Produce.class);
System.err.println(response.toString());
12:分頁查詢
我們使用match_all進(jìn)行全部搜索的時(shí)候使用size關(guān)鍵字設(shè)置每一頁的大小,使用from關(guān)鍵字設(shè)置頁碼
from的計(jì)算公式:(頁碼-1)*size
//分頁查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q->q.matchAll(v->v)).size(2).from(0),
Produce.class);
System.err.println(response.hits().hits());
12-1:使用分頁時(shí),最多返回10000條。需要進(jìn)行設(shè)置
//分頁查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q->q.matchAll(v->v))
.size(2)
.from(0)
.trackTotalHits(t->t.enabled(true)),
Produce.class);
System.err.println(response.hits().hits());
13:排序
使用sort關(guān)鍵字指定需要進(jìn)行排序的字段設(shè)置排序類型即可,我們這里會(huì)使用到SortOrder枚舉類來進(jìn)行指定排序方式
desc:降序
asc:升序文章來源:http://www.zghlxwxcb.cn/news/detail-848232.html
//排序
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q->q.matchAll(v->v))
.sort(builder1 -> builder1.field(f->f.field("price").order(SortOrder.Asc))),
Produce.class);
System.err.println(response.hits().hits());
14:指定字段查詢
使用_source關(guān)鍵字在數(shù)組中設(shè)置需要展示的字段
值得注意的是在source方法中需要我們寫filter去指定是include包含或是exclude去除xx字段文章來源地址http://www.zghlxwxcb.cn/news/detail-848232.html
//指定字段查詢
final SearchResponse<Produce> response = client.search(builder ->
builder.index("produces")
.query(q->q.matchAll(v->v))
.source(s->s.filter(v->v.includes("price","des"))),
Produce.class);
System.err.println(response.hits().hits());
15:聚合查詢-求最大值
SearchResponse<Object> search = elasticsearchClient.search(builder ->
builder.index("my_test_index")
.from(0)
.size(1)
.aggregations("aa", t ->
t.max(f->
f.field("type"))), Object.class);
EsResult esResult = EsUtils.searchAnalysis(search);
Aggregate aa = esResult.getAggregations().get("aa");
LongTermsAggregate lterms = aa.lterms();
Buckets<LongTermsBucket> buckets = lterms.buckets();
List<LongTermsBucket> array = buckets.array();
16:桶聚合查詢-劣勢(shì) group by
SearchResponse<JSONObject> search = elasticsearchClient.search(builder ->
builder.index(EsIndexConstants.article_info)
.query(t->
t.range(f->
f.field("create_time")
.gte(JsonData.of(startDate))
.lte(JsonData.of(endDate))))
.from(0)
.size(1)
.aggregations("countValue", t ->
t.terms(f -> f.field("ata_type.keyword")))
, JSONObject.class);
Aggregate countValue = search .getAggregations().get("countValue");
List<StringTermsBucket> array = countValue.sterms().buckets().array();
到了這里,關(guān)于Elasticsearch-06-Elasticsearch Java API Client-Elasticsearch 8.0 的api的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!