作者:ChenZhen
本人不常看網(wǎng)站消息,有問題通過下面的方式聯(lián)系:
- 郵箱:1583296383@qq.com
- vx: ChenZhen_7
我的個人博客地址:https://www.chenzhen.space/??
版權(quán):本文為博主的原創(chuàng)文章,本文版權(quán)歸作者所有,轉(zhuǎn)載請附上原文出處鏈接及本聲明。??
如果對你有幫助,請給一個小小的star???
Elasticsearch:使用最新的 Elasticsearch Java client 8.0 來創(chuàng)建索引并搜索
我們在學(xué)習(xí)ES客戶端時,一直使用的都是Java High Level Rest Client
,在瀏覽官網(wǎng)時,發(fā)現(xiàn)官方給出的警告是:Java REST
客戶端已被棄用,取而代之的是 Java API client
客戶端,ES 8.x
新版本中,Type
概念被棄用,所以新版 JavaAPI
也相應(yīng)做出了改變,使用更加簡便。ES 官方從 7.15
起開始建議使用新的 JavaAPI
如何使用最新的 Elasticsearch Java client 8.0
來創(chuàng)建索引并進行搜索。最新的 Elasticsearch Java client API
和之前的不同。在es7
的一些教程中,經(jīng)常使用 High Level API
來進行操作。但在官方文檔中,已經(jīng)顯示為 deprecated
。
Java API Client
官網(wǎng)為啥又推出一個新的客戶端接口呢,這是為了統(tǒng)一管理,官網(wǎng)給出的回應(yīng)是:將來只對這個客戶端進行維護改進,這也接口會更加的清晰明了,可讀性更高,更易于上手,更簡單!代碼看著更加簡潔了!
無論是ElasticsearchTemplate
類還是ElasticsearchRepository
接口,都是對ES常用的簡單功能進行封裝,在實際使用時,復(fù)雜的查詢語法還是依賴ElasticsearchClient
和原生的API封裝;
更詳細(xì)內(nèi)容參考官網(wǎng)API文檔:https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/package-structure.html
開始
本文將指導(dǎo)您完成 Java 客戶端的安裝過程,向您展示如何實例化客戶端,以及如何使用它執(zhí)行基本的 Elasticsearch
操作。
安裝
在項目的 pom.xml 中,添加以下存儲庫定義和依賴項:
<project>
<dependencies>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.10.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
</dependencies>
</project>
你可以使用API密鑰和Elasticsearch端點來連接到Elastic 。
-
RestClient
這個類主要是用作于與服務(wù)端IP以及端口的配置,在其的builder()
方法可以設(shè)置登陸權(quán)限的賬號密碼、連接時長等等。總而言之就是服務(wù)端配置。 -
RestClientTransport
這是Jackson
映射器創(chuàng)建傳輸。建立客戶端與服務(wù)端之間的連接傳輸數(shù)據(jù)。這是在創(chuàng)建ElasticsearchClient
需要的參數(shù),而創(chuàng)建RestClientTransport
就需要上面創(chuàng)建的RestClient
。 -
ElasticsearchClient
這個就是Elasticsearch
的客戶端。調(diào)用Elasticsearch
語法所用到的類,其就需要傳入上面介紹的RestClientTransport
。
// URL和API密鑰
String serverUrl = "https://localhost:9200";
String apiKey = "VnVhQ2ZHY0JDZGJrU...";
// 創(chuàng)建低級別客戶端
RestClient restClient = RestClient
.builder(HttpHost.create(serverUrl))
.setDefaultHeaders(new Header[]{
new BasicHeader("Authorization", "ApiKey " + apiKey)
})
.build();
// 使用Jackson映射器創(chuàng)建傳輸
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());
// 然后創(chuàng)建API客戶端
ElasticsearchClient client = new ElasticsearchClient(transport);
得到API客戶端對象client
,你就可以進行Elasticsearch
的基本操作,以下是一些最基本的操作。
簡單操作
創(chuàng)建索引
下面的代碼片段顯示了如何使用 indices
命名空間客戶端創(chuàng)建索引(lambda
語法在下面進行了說明):
// 創(chuàng)建索引products
client.indices().create(c -> c.index("products"));
判斷索引是否存在并創(chuàng)建索引(構(gòu)建器寫法與lambda寫法)
lambda寫法:
// 獲取【索引客戶端對象】
ElasticsearchIndicesClient indexClient = client.indices();
boolean flag = indexClient.exists(req -> req.index(iName)).value();
//CreateIndexResponse createIndexResponse = null;
boolean result = false;
if (flag) {
// 目標(biāo)索引已存在
log.info("索引【" + iName + "】已存在!");
} else {
// 不存在
result = indexClient.create(req -> req.index(iName)).acknowledged();
if (result) {
log.info("索引【" + iName + "】創(chuàng)建成功!");
} else {
log.info("索引【" + iName + "】創(chuàng)建失?。?);
}
}
構(gòu)建器寫法
// 獲取【索引客戶端對象】
ElasticsearchIndicesClient indexClient = client.indices();
//1、構(gòu)建【存在請求對象】
ExistsRequest existsRequest = new ExistsRequest.Builder().index(indexName).build();
//2、判斷目標(biāo)索引是否存在
boolean flag = indexClient.exists(existsRequest).value();
if (flag) {
// 目標(biāo)索引已存在
log.info("索引【" + iName + "】已存在!");
} else {
//1. 獲取【創(chuàng)建索引請求對象】
CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder().index(indexName).build();
//2. 創(chuàng)建索引,得到【創(chuàng)建索引響應(yīng)對象】
CreateIndexResponse createIndexResponse = indexClient.create(createIndexRequest);
createIndexResponse = indexClient.create(req -> req.index(indexName));
//System.out.println("創(chuàng)建索引響應(yīng)對象:" + createIndexResponse);
boolean result= indexName.acknowledged();
if (result) {
log.info("索引【" + indexName + "】創(chuàng)建成功!");
} else {
log.info("索引【" + indexName + "】創(chuàng)建失敗!");
}
}
可以看到構(gòu)建器寫法在簡潔度上完全不如lambda表達式,接下來所有例子均只采用lambda寫法
查詢索引
Map<String, IndexState> result = client.indices().get(req -> req.index("indexName")).result();
查詢?nèi)克饕?/h3>
Set<String> all = client.indices().get(req -> req.index("*")).result().keySet();
刪除索引
Boolean isDelete = client.indices().delete(req -> req.index("indexName")).acknowledged();
if(isDelete ) {
log.info("刪除索引成功");
} else {
log.info("刪除索引失敗");
}
插入文檔
Set<String> all = client.indices().get(req -> req.index("*")).result().keySet();
Boolean isDelete = client.indices().delete(req -> req.index("indexName")).acknowledged();
if(isDelete ) {
log.info("刪除索引成功");
} else {
log.info("刪除索引失敗");
}
生成請求的最直接方法是使用流暢的 DSL。在下面的示例中,我們使用產(chǎn)品的 SKU 作為索引中的文檔標(biāo)識符,在產(chǎn)品索引中為products描述編制索引。product對象將使用 Elasticsearch 客戶端上配置的對象映射器映射到 JSON。
Product product = new Product("bk-1", "City bike", 123.0);
IndexResponse response = client.index(i -> i
.index("products")
.id(product.getSku())
.document(product)
);
logger.info("Indexed with version " + response.version());
還可以將使用 DSL 創(chuàng)建的對象分配給變量。Java API 客戶端類有一個靜態(tài)of() 方法,它使用 DSL 語法創(chuàng)建一個對象。
Product product = new Product("bk-1", "City bike", 123.0);
IndexRequest<Product> request = IndexRequest.of(i -> i
.index("products")
.id(product.getSku())
.document(product)
);
IndexResponse response = client.index(request);
logger.info("Indexed with version " + response.version());
使用原始 JSON 數(shù)據(jù)
當(dāng)您要索引的數(shù)據(jù)來自外部源時,對于半結(jié)構(gòu)化數(shù)據(jù),必須創(chuàng)建域?qū)ο罂赡軙苈闊┗蛲耆豢赡堋?/p>
您可以使用 withJson() 為任意源的數(shù)據(jù)編制索引。使用此方法將讀取源并將其用于索引請求的document屬性。
Reader input = new StringReader(
"{'@timestamp': '2022-04-08T13:55:32Z', 'level': 'warn', 'message': 'Some log message'}"
.replace('\'', '"'));
IndexRequest<JsonData> request = IndexRequest.of(i -> i
.index("logs")
.withJson(input)
);
IndexResponse response = client.index(request);
logger.info("Indexed with version " + response.version());
批量請求:多個文檔
批量請求允許在一個請求中發(fā)送多個與文檔相關(guān)的操作到 Elasticsearch。當(dāng)你有多個文檔需要導(dǎo)入時,這比分別發(fā)送每個文檔的請求更有效率。
一個批量請求可以包含多種類型的操作:
- 創(chuàng)建一個文檔,在確保它不存在后進行索引
- 索引一個文檔,如果需要則創(chuàng)建它,如果已經(jīng)存在則替換它
- 更新一個已經(jīng)存在的文檔,可以使用腳本或部分文檔
- 刪除一個文檔
一個批量請求包含一系列操作,每個操作都是一種類型,有幾個變種。為了創(chuàng)建這個請求,最方便的方法是使用主請求的構(gòu)建器對象以及每個操作的流暢式 DSL。
下面的示例展示了如何索引一個應(yīng)用程序?qū)ο罅斜怼?/p>
List<Product> products = fetchProducts();
BulkRequest.Builder br = new BulkRequest.Builder();
for (Product product : products) {
br.operations(op -> op
.index(idx -> idx
.index("products")
.id(product.getSku())
.document(product)
)
);
}
BulkResponse result = client.bulk(br.build());
// Log errors, if any
if (result.errors()) {
logger.error("Bulk had errors");
for (BulkResponseItem item: result.items()) {
if (item.error() != null) {
logger.error(item.error().reason());
}
}
}
批量的腳本操作:
for (ProductDTO productDTO : Optional.ofNullable(productResult.getData()).orElse(Collections.emptyList())) {
Map<String, JsonData> params = new HashMap<>(16);
params.put("buys", JsonData.of(productDTO.getBuys()));
params.put("views", JsonData.of(productDTO.getViews()));
params.put("comments", JsonData.of(productDTO.getComments()));
br.operations(op -> op
.update(u -> u
.id(String.valueOf(productDTO.getId()))
.index(searchProperties.getProductIndexName())
.action(a -> a
.script(s -> s
.inline(i -> i
.source("ctx._source.buys = params.buys;" +
"ctx._source.views = params.views;" +
"ctx._source.comments = params.comments;")
.params(params))))
)
);
}
查詢文檔
Elasticsearch主要用于搜索,但你也可以直接訪問文檔,通過id 。
下面的示例從"products
"索引中讀取id "bk-1"的文檔。
get請求有兩個參數(shù):
- 第一個參數(shù)是實際請求,使用DSL構(gòu)建
- 第二個參數(shù)是我們希望將文檔的JSON映射到的類。
GetResponse<Product> response = client.get(g -> g
.index("products")
.id("bk-1"),
Product.class
);
if (response.found()) {
Product product = response.source();
logger.info("產(chǎn)品名稱 " + product.getName());
} else {
logger.info("未找到產(chǎn)品");
}
這個get請求包括索引名稱和標(biāo)識符。
目標(biāo)類,在這里是
Product
。
如果你的索引包含半結(jié)構(gòu)化數(shù)據(jù),或者如果你沒有對象的定義,你也可以將文檔作為原始JSON數(shù)據(jù)來讀取。
原始JSON數(shù)據(jù)只是另一個類,你可以將其用作get請求的結(jié)果類型。在下面的示例中,我們使用了Jackson
的ObjectNode
。我們也可以使用任何可以由與ElasticsearchClient
關(guān)聯(lián)的JSON映射器反序列化的JSON表示。
GetResponse<ObjectNode> response = client.get(g -> g
.index("products")
.id("bk-1"),
ObjectNode.class
);
if (response.found()) {
ObjectNode json = response.source();
String name = json.get("name").asText();
logger.info("產(chǎn)品名稱 " + name);
} else {
logger.info("未找到產(chǎn)品");
}
1.目標(biāo)類是一個原始的JSON對象。
修改文檔
UpdateResponse updateResponse = client.update(u -> u
.doc(textBook)
.id(id),
TextBook.class
刪除文檔
DeleteResponse deleteResponse = client.delete(d -> d
.index(index)
.id(id)
);
命名空間
在REST API文檔中,數(shù)量眾多API是按照特性(feature
)來分組的,如下圖:
在ES的Java庫Java API Client
中,上圖中的各種feature
被稱為namespace
在ES的Java庫Java API Client
中,與REST API對應(yīng)的的類和接口都在統(tǒng)一的包名co.elastic.clients.elasticsearch
之下,然后再通過下一級package
進行分類,這個分類與上圖的feature
相對應(yīng)。例如索引相關(guān)的,在REST API中的feature
是Index APIs
,那么在Java API Client
中,完整的package
就是co.elastic.clients.elasticsearch.indices
,這里面有索引操作所需的請求、響應(yīng)、服務(wù)等各種類.
每一個namespace
(也就是REST API中的feature
),都有自己的client
,例如索引相關(guān)的操作都有索引專用的client
類負(fù)責(zé),client.indices()
返回的是ElasticsearchIndicesClient
對象,這是索引操作專用的實例
ElasticsearchClient client = ......
client.indices().create(c -> c.index("products"));
展開上述代碼的indices()
方法,看看其內(nèi)部實現(xiàn),如下所示,每次調(diào)用indices
方法,都會創(chuàng)建一個ElasticsearchIndicesClient
對象,對于其他namespace
,例如ingest
、license
亦是如此,都會創(chuàng)建新的實例
每個namespace
都有自己的client
,但也有例外,就是search
和document
,它們的代碼不在search
或者document
這樣的package
下面,而是在core
下面,而且可以直接通過ElasticsearchClient
來操作,如下:
插入一條文檔:
Product product = new Product("bk-1", "City bike", 123.0);
IndexResponse response = client.index(i -> i
.index("products")
.id(product.getSku())
.document(product)
);
logger.info("Indexed with version " + response.version());
構(gòu)建 API 對象
1.構(gòu)建器模式
ElasticsearchClient client = ......
CreateIndexResponse createResponse = client.indices().create(
new CreateIndexRequest.Builder()
.index("my-index")
.aliases("foo",
new Alias.Builder().isWriteIndex(true).build()
)
.build()
);
2.lambda 表達式
雖然這效果很好,但必須實例化構(gòu)建器類并調(diào)用 build()
方法有點冗長。因此,Java API 客戶端中的每個屬性設(shè)置器也接受一個 lambda
表達式,該表達式將新創(chuàng)建的構(gòu)建器作為參數(shù),并返回填充的構(gòu)建器。上面的片段也可以寫成:
ElasticsearchClient client = ......
CreateIndexResponse createResponse = client.indices()
.create(createIndexBuilder -> createIndexBuilder
.index("my-index")
.aliases("foo", aliasBuilder -> aliasBuilder
.isWriteIndex(true)
)
);
這種方法允許更簡潔的代碼,并且還避免了導(dǎo)入類(甚至記住它們的名稱),因為類型是從方法參數(shù)簽名推斷出來的。建議大家這樣去寫,非常簡潔快速,后面的各種操作我也會用這種方式來書寫。
生成器 lambda 對于復(fù)雜的嵌套查詢(如下所示)特別有用
{
"query": {
"intervals": {
"field": "my_text",
"all_of": [
{
"ordered": true,
"intervals": [
{
"match": {
"query": "my favorite food",
"max_gaps": 0,
"ordered": true
}
}
]
},
{
"any_of": {
"intervals": [
{
"match": {
"query": "hot water"
}
},
{
"match": {
"query": "cold porridge"
}
}
]
}
}
]
}
}
}
對應(yīng)的代碼如下:
SearchResponse<SomeApplicationData> results = client
.search(b0 -> b0
.query(b1 -> b1
.intervals(b2 -> b2
.field("my_text")
.allOf(b3 -> b3
.ordered(true)
.intervals(b4 -> b4
.match(b5 -> b5
.query("my favorite food")
.maxGaps(0)
.ordered(true)
)
)
.intervals(b4 -> b4
.anyOf(b5 -> b5
.intervals(b6 -> b6
.match(b7 -> b7
.query("hot water")
)
)
.intervals(b6 -> b6
.match(b7 -> b7
.query("cold porridge")
)
)
)
)
)
)
),
SomeApplicationData.class
);
復(fù)雜查詢
搜索查詢
有許多類型的搜索查詢可以組合使用。我們將從簡單的文本匹配查詢開始,在products
索引中搜索自行車
。我們在這里選擇匹配查詢(全文搜索)
- 搜索結(jié)果具有
hits
屬性,其中包含與查詢匹配的文檔以及有關(guān)索引中存在的匹配項總數(shù)的信息。 - 總值帶有一個關(guān)系,該關(guān)系指示總值是精確的(eq — 相等)還是近似的(gte — 大于或等于)。
- 每個返回的文檔都附帶其相關(guān)性分?jǐn)?shù)以及有關(guān)其在索引中的位置的其他信息。
String searchText = "自行車";
SearchResponse<Product> response = client.search(s -> s
.index("products")
.query(q -> q
.match(t -> t
.field("name")
.query(searchText)
)
),
Product.class
);
TotalHits total = response.hits().total();//total可以獲取結(jié)果的總數(shù)
boolean isExactResult = total.relation() == TotalHitsRelation.Eq;
if (isExactResult) {
logger.info("找到 " + total.value() + " 個結(jié)果");
} else {
logger.info("找到超過 " + total.value() + " 個結(jié)果");
}
List<Hit<Product>> hits = response.hits().hits();
for (Hit<Product> hit: hits) {
Product product = hit.source();
logger.info("找到產(chǎn)品 " + product.getSku() + ",得分 " + hit.score());
}
與獲取操作類似,您可以使用相應(yīng)的目標(biāo)類而不是 Product(如 JSON-P 的 JsonValue 或 Jackson 的 ObjectNode)將匹配查詢的文檔作為原始 JSON 獲取.
bool查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> boolSearch = client.search(s -> s
.index(index)
.query(q -> q
.bool(b -> b
.must(m -> m
.term(t -> t
.field("author")
.value("老壇")
)
)
.should(sh -> sh
.match(t -> t
.field("bookName")
.query("老壇")
)
)
)
),
TextBook.class);
for (Hit<TextBook> hit: boolSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的bool查詢,它等價的ES語法就是:
GET textbook/_search
{
"query":{
"bool":{
"should":{
"match":{
"bookName":"老壇"
}
},
"must":{
"term":{
"author":"老壇"
}
}
}
}
}
嵌套搜索查詢
在下面的示例中,我們將搜索最高價格為 200 的自行車。
Java API 客戶端Query
類有一個靜態(tài)of() 方法,它使用 DSL 語法創(chuàng)建一個對象。
String searchText = "自行車";
double maxPrice = 200.0;
// 根據(jù)產(chǎn)品名稱搜索
Query byName = MatchQuery.of(m -> m
.field("name")
.query(searchText)
)._toQuery();
// 根據(jù)最高價格搜索
Query byMaxPrice = RangeQuery.of(r -> r
.field("price")
.gte(JsonData.of(maxPrice))
)._toQuery();
// 組合產(chǎn)品名稱和價格查詢來搜索產(chǎn)品索引
SearchResponse<Product> response = client.search(s -> s
.index("products")
.query(q -> q
.bool(b -> b
.must(byName)
.must(byMaxPrice)
)
),
Product.class
);
List<Hit<Product>> hits = response.hits().hits();
for (Hit<Product> hit : hits) {
Product product = hit.source();
logger.info("找到產(chǎn)品 " + product.getSku() + ",得分 " + hit.score());
}
在大量并發(fā)頻繁執(zhí)行各種namespace操作時,會創(chuàng)建大量client對象,這樣會影響系統(tǒng)性能嗎?
官方說這是輕量級對象(very lightweight),所以,理論上可以放心創(chuàng)建,不必?fù)?dān)心其對系統(tǒng)造成的壓力
同時,這段代碼的目的是為了實現(xiàn)邏輯功能,代碼的可讀性和維護性通常比微小的內(nèi)存浪費更重要。如果通過將這段邏輯放在條件塊內(nèi),來避免不使用的 boolQueryBuilder
,可能會使代碼更復(fù)雜和難以閱讀。
拼接查詢條件
可以選擇性的拼接條件,我們先創(chuàng)建一個SearchRequest.Builder
請求對象構(gòu)建器,然后拼接條件。
// 1. 創(chuàng)建查詢構(gòu)建器
co.elastic.clients.elasticsearch.core.SearchRequest.Builder searchBuilder =
new co.elastic.clients.elasticsearch.core.SearchRequest.Builder();
//設(shè)置索引名稱
searchBuilder
.index(searchProperties.getProductIndexName());
if (StrUtil.isBlank(request.getKey())) {
Query query = Query.of(q -> q
.bool(b -> b
.must(m -> m.matchAll(m1 -> m1))));
} else {
Query query = Query.of(q -> q
.bool(b1 -> b1
.should(s -> s
.matchPhrase(m1 -> m1
.field("productName").query(request.getKey()).boost(3f)))
.should(s1 -> s1
.matchPhrase(m2 -> m2
.field("shopName").query(request.getKey())))
.should(s2 -> s2
.matchPhrase(m3 -> m3
.field("brandName").query(request.getKey())))
));
//根據(jù)條件拼接不同query
searchBuilder
.query(query);
//查詢
co.elastic.clients.elasticsearch.core.SearchRequest searchRequest = searchBuilder.build();
SearchResponse<ProductDocument> response = client.search(searchRequest, ProductDocument.class);
term查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> termSearch = client.search(s -> s
.index(index)
.query(q -> q
.term(t -> t
.field("bookName")
.value("老壇")
)
),
TextBook.class);
for (Hit<TextBook> hit: termSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的term查詢,它等價的ES語法就是:
GET textbook/_search
{
"query": {
"term": {
"bookName":"老壇"
}
}
}
terms查詢
List<Long> skuIds = new ArrayList<>();
skuIds.add(1L);
skuIds.add(2L);
skuIds.add(3L);
// 創(chuàng)建 "skuIds" 條件tems查詢
TermsQuery bySkuIds = TermsQuery.of(t -> t
.field("skuIds")
.terms(t2 -> t2
.value(skuIds.stream().map(FieldValue::of).collect(Collectors.toList())))
);
//查詢命令
SearchResponse<ActivityDocument> search = client.search(s -> s
.index("activity")
.query(q -> q
.terms(bySkuIds)
)
, ActivityDocument.class);
對應(yīng)了ES的terms查詢,它等價的ES語法就是:
{
"query": {
"terms": {
"skuIds ": [1,2,3]
}
}
}
match_phrase查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> matchPhraseSearch = client.search(s -> s
.index(index)
.query(q -> q
.matchPhrase(m -> m
.field("bookName")
.query("老壇")
)
),
TextBook.class);
for (Hit<TextBook> hit: matchPhraseSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的match_phrase查詢,它等價的ES語法就是:
GET textbook/_search
{
"query": {
"match_phrase": {
"bookName":"老壇"
}
}
}
multi_match查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> multiMatchSearch = client.search(s -> s
.index(index)
.query(q -> q
.multiMatch(m -> m
.query("老壇")
.fields("author", "bookName")
)
),
TextBook.class);
for (Hit<TextBook> hit: multiMatchSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的multi_match查詢,它等價的ES語法就是:
GET textbook/_search
{
"query": {
"multi_match": {
"query": "老壇",
"fields": ["author","bookName"]
}
}
}
fuzzy查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> fuzzySearch = client.search(s -> s
.index(index)
.query(q -> q
.fuzzy(f -> f
.field("bookName")
.fuzziness("2")
.value("老壇")
)
),
TextBook.class);
for (Hit<TextBook> hit: fuzzySearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的fuzzy查詢,它等價的ES語法就是:
GET textbook/_search
{
"query": {
"fuzzy": {
"bookName":{
"value":"老壇",
"fuzziness":2
}
}
}
}
range查詢
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> rangeSearch = client.search(s -> s
.index(index)
.query(q -> q
.range(r -> r
.field("bookName")
.gt(JsonData.of(20))
.lt(JsonData.of(20))
)
),
TextBook.class);
for (Hit<TextBook> hit: rangeSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
對應(yīng)了ES的range查詢,它等價的ES語法就是:
GET textbook/_search
{
"query": {
"range": {
"bookName": {
"gt":20,
"lt":30
}
}
}
}
高亮查詢
實現(xiàn)很簡單,請注意,我們定義 HighlightField
即hf
,即我們要突出顯示的字段。
在這個 HighlightField
中,我們還定義了參數(shù),包括numberOfFragments
和fragmentSize
。
參數(shù)可以設(shè)置在highlight
的下一級,此時為全局設(shè)置(如下面的fragmentSize(50)
和numberOfFragments(5)
),也可以設(shè)置在字段的下一級,此時為字段設(shè)置。單個字段的設(shè)置優(yōu)先級高于全局設(shè)置。
var response = client.search(s -> s
.index("product")
.query(q -> q.multiMatch(m -> m.fields(List.of("title", "description")).query("Aliens and predator")))
.highlight(h -> h
.type(HighlighterType.Unified)
.fields("title",hf -> hf
.numberOfFragments(0))
.fields("description",hf -> hf
.numberOfFragments(4).fragmentSize(50))
.fragmentSize(50)
.numberOfFragments(5)
)
, Movie.class);
上面的寫法等同于:
Map<String, HighlightField> map = new HashMap<>();
map.put("title", HighlightField.of(hf -> hf.numberOfFragments(0)));
map.put("description", HighlightField.of(hf -> hf.numberOfFragments(4).fragmentSize(50)));
Highlight highlight = Highlight.of(
h -> h.type(HighlighterType.Unified)
.fields(map)
.fragmentSize(50)
.numberOfFragments(5)
);
var response = client.search(s -> s
.index("idx_movies")
.query(q -> q.multiMatch(m -> m.fields(List.of("title", "description")).query("Aliens and predator")))
.highlight(highlight)
, Movie.class);
排序和分頁
排序和分頁直接像ES的語法一樣,體現(xiàn)在和query的平級即可。這里已match為例進行介紹。
@SpringBootTest
@Slf4j
public class ESTest {
@Resource
ElasticsearchClient client;
String index = "textbook";
@Test
public void grepTextBook() throws IOException {
SearchResponse<TextBook> matchSearch = client.search(s -> s
.index(index)
.query(q -> q
.match(t -> t
.field("bookName")
.query("老壇")
)
)
.from(1)
.size(100)
.sort(so -> so // 排序操作項
.field(f -> f // 排序字段規(guī)則
.field("num")
.order(SortOrder.Desc)
)
),
TextBook.class);
for (Hit<TextBook> hit: matchSearch.hits().hits()) {
TextBook pd = hit.source();
System.out.println(pd);
}
}
}
這是一個根據(jù)num字段進行降序排序的查詢,按頁容量為100對數(shù)據(jù)進行分頁,取第二頁數(shù)據(jù)。
它等價的ES語法就是:
GET textbook/_search
{
"query":{
"match":{
"bookName":"老壇"
}
},
"from":1,
"size":100,
"sort":{
"num":{
"order":"desc"
}
}
}
聚合
這個示例是一種用于分析的聚合操作,我們不需要使用匹配的文檔。用于分析的搜索請求通常的一般模式是將結(jié)果大小設(shè)置為0
,將搜索結(jié)果的目標(biāo)類設(shè)置為 Void
。
如果同樣的聚合用于顯示產(chǎn)品和價格直方圖作為鉆取細(xì)分,我們會將大小設(shè)置為非零值,并使用 Product
作為目標(biāo)類來處理結(jié)果。
String searchText = "自行車";
Query query = MatchQuery.of(m -> m
.field("name")
.query(searchText)
)._toQuery();
SearchResponse<Void> response = client.search(b -> b
.index("products")
.size(0) // 將匹配文檔數(shù)量設(shè)置為零,因為我們只關(guān)心價格直方圖
.query(query) // 設(shè)置用于過濾要執(zhí)行聚合的產(chǎn)品的查詢
.aggregations("price-histogram", a -> a
.histogram(h -> h
.field("price")
.interval(50.0)
)
),
Void.class
);
在上面的代碼中,我們首先創(chuàng)建了一個用于產(chǎn)品名稱匹配的查詢,然后執(zhí)行了一個搜索請求,其中包含了一個名為 “price-histogram” 的聚合操作,用于創(chuàng)建價格直方圖。我們將結(jié)果大小設(shè)置為零,因為我們只關(guān)心聚合結(jié)果,不需要匹配的文檔。
響應(yīng)包含了每個請求中的聚合結(jié)果。
List<HistogramBucket> buckets = response.aggregations()
.get("price-histogram")
.histogram()
.buckets().array();
for (HistogramBucket bucket : buckets) {
logger.info("有 " + bucket.docCount() +
"輛自行車的價格低于 " + bucket.key());
}
獲取 “price-histogram” 聚合的結(jié)果。
將其轉(zhuǎn)換為直方圖變體的結(jié)果。這必須與聚合定義保持一致。
桶可以表示為數(shù)組或映射。這里將其轉(zhuǎn)換為數(shù)組變體(默認(rèn)選項)。
另一個例子
// Creating aggregations
SearchResponse<Void> search3 = client.search( b-> b
.index("products")
.size(0)
.aggregations("price-histo", a -> a
.histogram(h -> h
.field("price")
.interval(20.0)
)
),
Void.class
);
long firstBucketCount = search3.aggregations()
.get("price-histo")
.histogram()
.buckets().array()
.get(0)
.docCount();
System.out.println("doc count: " + firstBucketCount);
}
上面的 aggregation 相當(dāng)于如下的請求:
GET products/_search
{
"size": 0,
"aggs": {
"price-histo": {
"histogram": {
"field": "price",
"interval": 20
}
}
}
}
我們的 Java 代碼的輸出結(jié)果為:
doc count: 2
上面的聚合,我們可以甚至直接使用 JSON 結(jié)構(gòu)的字符串來進行操作:
String aggstr = "\n" +
" { \n" +
" \"size\": 0, \n" +
" \"aggs\": { \n" +
" \"price-histo\": { \n" +
" \"histogram\": { \n" +
" \"field\": \"price\", \n" +
" \"interval\": 20 \n" +
" } \n" +
" } \n" +
" } \n" +
" } ";
System.out.println("agg is: " + aggstr );
InputStream agg = new ByteArrayInputStream(aggstr.getBytes());
SearchResponse<Void> searchAgg = client
.search(b -> b
.index("products")
.withJson(agg),
Void.class
);
firstBucketCount = searchAgg.aggregations()
.get("price-histo")
.histogram()
.buckets().array()
.get(0)
.docCount();
System.out.println("doc count: " + firstBucketCount);
上面代碼顯示的結(jié)果和之上的結(jié)果是一樣的:
分組查詢
Elasticsearch Java API Client客戶端中的分組查詢,也是屬于聚合查詢的一部分,所以同樣使用aggregations
方法,并使用terms
方法來代表分組查詢,field
傳入需要分組的字段,最后通過響應(yīng)中的aggregations
參數(shù)來獲取,這里需要根據(jù)數(shù)據(jù)的類型來獲取最后的分組結(jié)果,我這里因為統(tǒng)計的是數(shù)字類型,所以調(diào)用lterms()
使用LongTermsAggregate
來獲取結(jié)果,同理:如果是String
類型則調(diào)用sterms()
使用StringTermsAggregate
,最后打印出docCount
屬性即可。
SearchResponse<Test> response11 = client.search(s -> s
.index("newapi")
.size(100)
.aggregations("ageGroup", a -> a
.terms(t -> t
.field("age")
)
)
, Test.class);
System.out.println(response11.took());
System.out.println(response11.hits().total().value());
response11.hits().hits().forEach(e -> {
System.out.println(e.source().toString());
});
Aggregate aggregate = response11.aggregations().get("ageGroup");
LongTermsAggregate lterms = aggregate.lterms();
Buckets<LongTermsBucket> buckets = lterms.buckets();
for (LongTermsBucket b : buckets.array()) {
System.out.println(b.key() + " : " + b.docCount());
}
過濾器
SourceConfig 提供對包含和排除字段的訪問權(quán)限。
SearchResponse<ActivityDocument> search = client.search(s -> s
.query(query)
.source(s1 -> s1
.filter(v -> v
.includes("type", "allProdsFlag", "price", "discount", "marketingType", "marketingCalType", "number", "name", "shopId", "pic", "startTime", "endTime", "skuIds", "activityId")
.excludes(null)
)
)
, ActivityDocument.class);
或者使用of
來構(gòu)建
SourceConfig sourceConfig = SourceConfig.of(s -> s
.filter(v -> v
.includes("type", "allProdsFlag", "price", "discount", "marketingType", "marketingCalType", "number", "name", "shopId", "pic", "startTime", "endTime", "skuIds", "activityId")
.excludes(null)
)
);
SearchResponse<ActivityDocument> search = client.search(s -> s
.query(query)
.source(sourceConfig)
, ActivityDocument.class);
參考文章與推薦閱讀
https://www.google.com/
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/getting-started-java.html#_indexing_documents
java與es8實戰(zhàn)之三:Java API Client有關(guān)的知識點串講
https://juejin.cn/post/7080726607043756045文章來源:http://www.zghlxwxcb.cn/news/detail-756077.html
https://medium.com/search?q=java+api+client文章來源地址http://www.zghlxwxcb.cn/news/detail-756077.html
到了這里,關(guān)于最新版ES8的client API操作 Elasticsearch Java API client 8.0的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!