Elasticsearch Java高级客户端

1.  概述

Java REST Client 有两种风格:

  • Java Low Level REST Client :用于Elasticsearch的官方低级客户端。它允许通过http与Elasticsearch集群通信。将请求编排和响应反编排留给用户自己处理。它兼容所有的Elasticsearch版本。(PS:学过WebService的话,对编排与反编排这个概念应该不陌生。可以理解为对请求参数的封装,以及对响应结果的解析)
  • Java High Level REST Client :用于Elasticsearch的官方高级客户端。它是基于低级客户端的,它提供很多API,并负责请求的编排与响应的反编排。(PS:就好比是,一个是传自己拼接好的字符串,并且自己解析返回的结果;而另一个是传对象,返回的结果也已经封装好了,直接是对象,更加规范了参数的名称以及格式,更加面对对象一点)

(PS:所谓低级与高级,我觉得一个很形象的比喻是,面向过程编程与面向对象编程)

在 Elasticsearch 7.0 中不建议使用TransportClient,并且在8.0中会完全删除TransportClient。因此,官方更建议我们用Java High Level REST Client,它执行HTTP请求,而不是序列号的Java请求。既然如此,这里就直接用高级了。

 

2.  Java High Level REST Client (高级REST客户端)

2.1.  Maven仓库

<dependency>
    groupId>org.elasticsearch.client</artifactId>elasticsearch-rest-high-level-clientversion>6.5.4>
>

2.2.  依赖

  • org.elasticsearch.client:elasticsearch-rest-client
  • org.elasticsearch:elasticsearch

2.3.  初始化

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost",9200,"http"),

高级客户端内部会创建低级客户端用于基于提供的builder执行请求。低级客户端维护一个连接池,并启动一些线程,因此当你用完以后应该关闭高级客户端,并且在内部它将会关闭低级客户端,以释放这些资源。关闭客户端可以使用close()方法:

client.close();

 

2.4.  文档API

2.4.1.  添加文档

IndexRequest

IndexRequest request = new IndexRequest("posts","doc","1");
String jsonString = "{\"user\":\"kimchy\",\"postDate\":\"2013-01-30\",\"message\":\"trying out Elasticsearch\"}";
request.source(jsonString,XContentType.JSON);

提供文档source的方式还有很多,比如:

通过Map的方式提供文档source

通过XContentBuilder方式提供source

通过Object的方式(键值对)提供source

可选参数

同步执行

异步执行

你也可以异步执行 IndexRequest,为此你需要指定一个监听器来处理这个异步响应结果:

一个典型的监听器看起来是这样的:

IndexResponse

如果有版本冲突,将会抛出ElasticsearchException

同样的异常也有可能发生在当opType设置为create的时候,且相同索引、相同类型、相同ID的文档已经存在时。例如:

 

2.4.2.  查看文档

Get Request

可选参数

同步执行

异步执行

Get Response

当索引不存在,或者指定的文档的版本不存在时,响应状态吗是404,并且抛出ElasticsearchException

 

2.4.3.  文档是否存在

 

2.4.4.  删除文档

Delete Request

可选参数

同添加

 

2.5.  搜索API

Search Request

基本格式是这样的:

大多数查询参数被添加到 SearchSourceBuilder

可选参数

SearchSourceBuilder

控制检索行为的大部分选项都可以在SearchSourceBuilder中设置。下面是一个常见选项的例子:

在这个例子中,我们首先创建了一个SearchSourceBuilder对象,并且带着默认选项。然后设置了一个term查询,接着设置检索的位置和数量,最后设置超时时间

在设置完这些选项以后,我们只需要把SearchSourceBuilder加入到SearchRequest中即可

 

构建Query

用QueryBuilder来创建Serarch Query。QueryBuilder支持Elasticsearch DSL中每一种Query

例如:

还可以通过QueryBuilders工具类来创建QueryBuilder对象,例如:

无论是用哪种方式创建,最后一定要把QueryBuilder添加到SearchSourceBuilder中

 

排序

SearchSourceBuilder 可以添加一个或多个 SortBuilder

SortBuilder有四种实现:FieldSortBuilder、GeoDistanceSortBuilder、ScoreSortBuilder、ScriptSortBuilder

 

聚集函数

同步执行

异步执行

从查询响应中取出文档

 

3.  示例

3.1.  准备数据

3.1.1.  安装IK分词器插件

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip

3.1.2.  创建索引

curl -X PUT "localhost:9200/book" -H 'Content-Type: application/json' -d'
{
    mappings":{
        _doc:{
            properties:{
                id:{
                    type":integer
                },nametext,1)">analyzerik_max_wordsearch_analyzerauthorcategorypricedoublestatusshortsellReasonsellTimedateformatyyyy-MM-dd
                }
            }
        }
    }
}
'

3.1.3.  数据预览

 

3.2.  示例代码

3.2.1.  完整的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"modelVersion>4.0.0parent>
        >org.springframework.boot>spring-boot-starter-parent>2.1.1.RELEASErelativePath/> <!-- lookup parent from repository -->
    >com.cjs.example>elasticsearch-demo>0.0.1-SNAPSHOTnamedescription></>

    propertiesjava.version>1.8dependencies>
            >spring-boot-starter-web>spring-boot-starter-thymeleaf>

        >org.apache.commons>commons-lang3>3.8>com.alibaba>fastjson>1.2.54>ch.qos.logback>logback-core>1.2.3>org.projectlombok>lombokoptional>true>spring-boot-starter-testscope>testbuildpluginsplugin>
                >spring-boot-maven-plugin>

project>

3.2.2.  配置

package com.cjs.example.elasticsearch.config;

import org.apache.http.HttpHost;
 org.elasticsearch.client.RestClient;
 org.elasticsearch.client.RestHighLevelClient;
 org.springframework.context.annotation.Bean;
 org.springframework.context.annotation.Configuration;

/**
 * @author ChengJianSheng
 * @date 2019-01-07
 */
@Configuration
public class ElasticsearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client =  RestHighLevelClient(
                RestClient.builder(
                        )));
        return client;
    }

}

3.2.3.  domain

 com.cjs.example.elasticsearch.domain.model;

 lombok.Data;

 java.io.Serializable;


 * 图书
 * 
@Data
class BookModel implements Serializable {


    private Integer id;           图书ID

    private String name;          图书名称

    private String author;        作者

    private Integer category;     图书分类

    private Double price;         图书价格

    private String sellReason;    上架理由

    private String sellTime;        上架时间

    private Integer status;       状态(1:可售,0:不可售)

}

3.2.4.  Controller

 com.cjs.example.elasticsearch.controller;

 com.alibaba.fastjson.JSON;
 com.cjs.example.elasticsearch.domain.common.BaseResult;
 com.cjs.example.elasticsearch.domain.common.Page;
 com.cjs.example.elasticsearch.domain.model.BookModel;
 com.cjs.example.elasticsearch.domain.vo.BookRequestVO;
 com.cjs.example.elasticsearch.service.BookService;
 lombok.extern.slf4j.Slf4j;
 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;


 * 文档操作
 * 
@Slf4j
@RestController
@RequestMapping("/book")
 BookController {

    @Autowired
    private BookService bookService;

    
     * 列表分页查询
     
    @GetMapping("/list")
     BaseResult list(BookRequestVO bookRequestVO) {
        Page<BookModel> page = bookService.list(bookRequestVO);
        if (null == page) {
             BaseResult.error();
        }
         BaseResult.ok(page);
    }

    
     * 查看文档
     
    @GetMapping("/detail" BaseResult detail(Integer id) {
         id) {
            return BaseResult.error("ID不能为空");
        }
        BookModel book = bookService.detail(id);
         BaseResult.ok(book);
    }

    
     * 添加文档
     
    @PostMapping("/add" BaseResult add(@RequestBody BookModel bookModel) {
        bookService.save(bookModel);
        log.info("插入文档成功!请求参数: {}" BaseResult.ok();
    }

    
     * 修改文档
     
    @PostMapping("/update" BaseResult update(@RequestBody BookModel bookModel) {
        Integer id = bookModel.getId();
         book) {
            return BaseResult.error("记录不存在");
        }
        bookService.update(bookModel);
        log.info("更新文档成功!请求参数: {}"
     * 删除文档
     
    @GetMapping("/delete" BaseResult delete(Integer id) {
        );
        }
        bookService.delete(id);
         BaseResult.ok();
    }

}

3.2.5.  Service

 com.cjs.example.elasticsearch.service.impl;

 org.apache.commons.lang3.StringUtils;
 org.elasticsearch.action.ActionListener;
 org.elasticsearch.action.DocWriteResponse;
 org.elasticsearch.action.delete.DeleteRequest;
 org.elasticsearch.action.delete.DeleteResponse;
 org.elasticsearch.action.get.GetRequest;
 org.elasticsearch.action.get.GetResponse;
 org.elasticsearch.action.index.IndexRequest;
 org.elasticsearch.action.index.IndexResponse;
 org.elasticsearch.action.search.SearchRequest;
 org.elasticsearch.action.search.SearchResponse;
 org.elasticsearch.action.support.replication.ReplicationResponse;
 org.elasticsearch.action.update.UpdateRequest;
 org.elasticsearch.action.update.UpdateResponse;
 org.elasticsearch.client.RequestOptions;
 org.elasticsearch.common.unit.TimeValue;
 org.elasticsearch.index.query.BoolQueryBuilder;
 org.elasticsearch.index.query.QueryBuilders;
 org.elasticsearch.rest.RestStatus;
 org.elasticsearch.search.SearchHit;
 org.elasticsearch.search.SearchHits;
 org.elasticsearch.search.builder.SearchSourceBuilder;
 org.elasticsearch.search.sort.FieldSortBuilder;
 org.elasticsearch.search.sort.SortOrder;
 org.springframework.stereotype.Service;
 org.springframework.util.CollectionUtils;

 java.io.IOException;
import java.util.*;
 java.util.stream.Collectors;


@Slf4j
@Service
class BookServiceImpl  BookService {

    private static final String INDEX_NAME = "book";
    final String INDEX_TYPE = "_doc";

    @Autowired
     RestHighLevelClient client;


    @Override
    public Page<BookModel> list(BookRequestVO bookRequestVO) {
        int pageNo = bookRequestVO.getPageNo();
        int pageSize = bookRequestVO.getPageSize();

        SearchSourceBuilder sourceBuilder =  SearchSourceBuilder();
        sourceBuilder.from(pageNo - 1);
        sourceBuilder.size(pageSize);
        sourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));
        sourceBuilder.query(QueryBuilders.matchAllQuery());

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        if (StringUtils.isNotBlank(bookRequestVO.getName())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("name" (StringUtils.isNotBlank(bookRequestVO.getAuthor())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("author"null != bookRequestVO.getStatus()) {
            boolQueryBuilder.must(QueryBuilders.termQuery("status" (StringUtils.isNotBlank(bookRequestVO.getSellTime())) {
            boolQueryBuilder.must(QueryBuilders.termQuery("sellTime" (StringUtils.isNotBlank(bookRequestVO.getCategories())) {
            String[] categoryArr = bookRequestVO.getCategories().split(",");
            List<Integer> categoryList = Arrays.asList(categoryArr).stream().map(e->Integer.valueOf(e)).collect(Collectors.toList());
            BoolQueryBuilder categoryBoolQueryBuilder = QueryBuilders.boolQuery();
            for (Integer category : categoryList) {
                categoryBoolQueryBuilder.should(QueryBuilders.termQuery("category" SearchRequest();
        searchRequest.indices(INDEX_NAME);
        searchRequest.source(sourceBuilder);

        try {
            SearchResponse searchResponse = client.search(searchRequest,RequestOptions.DEFAULT);

            RestStatus restStatus = searchResponse.status();
            if (restStatus != RestStatus.OK) {
                return null;
            }

            List<BookModel> list = new ArrayList<>();
            SearchHits searchHits = searchResponse.getHits();
             (SearchHit hit : searchHits.getHits()) {
                String source = hit.getSourceAsString();
                BookModel book = JSON.parseObject(source,BookModel.);
                list.add(book);
            }

            long totalHits = searchHits.getTotalHits();

            Page<BookModel> page = new Page<>(pageNo,pageSize,totalHits,list);

            TimeValue took = searchResponse.getTook();
            log.info("查询成功!请求参数: {},用时{}毫秒" page;
        } catch (IOException e) {
            log.error("查询失败!原因: {}";
    }

    @Override
    void save(BookModel bookModel) {
        Map<String,Object> jsonMap = new HashMap<>();
        jsonMap.put("id" IndexRequest(INDEX_NAME,INDEX_TYPE,String.valueOf(bookModel.getId()));
        indexRequest.source(jsonMap);

        client.indexAsync(indexRequest,RequestOptions.DEFAULT,1)">new ActionListener<IndexResponse>() {
            @Override
             onResponse(IndexResponse indexResponse) {
                String index = indexResponse.getIndex();
                String type = indexResponse.getType();
                String id = indexResponse.getId();
                long version = indexResponse.getVersion();

                log.info("Index: {},Type: {},Id: {},Version: {}"if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) {
                    log.info("写入文档");
                } else  DocWriteResponse.Result.UPDATED) {
                    log.info("修改文档");
                }
                ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo();
                if (shardInfo.getTotal() != shardInfo.getSuccessful()) {
                    log.warn("部分分片写入成功");
                }
                if (shardInfo.getFailed() > 0) {
                     (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) {
                        String reason = failure.reason();
                        log.warn("失败原因: {}" onFailure(Exception e) {
                log.error(e.getMessage(),e);
            }
        });
    }

    @Override
     update(BookModel bookModel) {
        Map<String,1)">();
        jsonMap.put("sellReason" UpdateRequest(INDEX_NAME,String.valueOf(bookModel.getId()));
        request.doc(jsonMap);
         {
            UpdateResponse updateResponse = client.update(request,RequestOptions.DEFAULT);
        }  (IOException e) {
            log.error("更新失败!原因: {}"void delete(int id) {
        DeleteRequest request =  DeleteRequest(INDEX_NAME,String.valueOf(id));
         {
            DeleteResponse deleteResponse = client.delete(request,RequestOptions.DEFAULT);
            if (deleteResponse.status() == RestStatus.OK) {
                log.info("删除成功!id: {}" (IOException e) {
            log.error("删除失败!原因: {}"public BookModel detail( id) {
        GetRequest getRequest =  GetRequest(INDEX_NAME,1)"> {
            GetResponse getResponse = client.get(getRequest,1)"> (getResponse.isExists()) {
                String source = getResponse.getSourceAsString();
                BookModel book = JSON.parseObject(source,1)">);
                 book;
            }
        }  (IOException e) {
            log.error("查看失败!原因: {}";
    }
}

3.2.6.  页面

<!DOCTYPE htmlhtml lang="zh"headmeta charset="UTF-8"title>图书列表link rel="stylesheet" href="/bootstrap-4/css/bootstrap.min.css"="/bootstrap-table/bootstrap-table.css"script src="jquery-3.3.1.min.js"script="/bootstrap-4/js/bootstrap.min.js"="/bootstrap-table/bootstrap-table.js"="/bootstrap-table/locale/bootstrap-table-zh-CN.js">
        $(function(){

            $('#table).bootstrapTable({
                url: /book/listgetserver(res) {  // 加载服务器数据之前的处理程序,可以用来格式化数据。参数:res为从服务器请求到的数据。
                    var result = {};
                    result.total  res.data.totalCount;
                    result.rows  res.data.pageList;
                    return result;
                },pagination: true3 初始PageSize
                queryParams: (params) {
                     req  {
                        pageSize: params.limit,pageNo: params.offset + 1
                    };
                     req;
                },striped: idIDname名称author作者price单价sellTime上架时间status状态(value) {
                        if (value == ) {
                            return <span style="color: green">可售</span>;
                        } else {
                            <span style="color: red">不可售</span>;
                        }
                    }
                },1)">category分类10010中国当代小说else 10011武侠小说10012爱情小说10013中国当代随笔sellReason上架理由操作() {
                        <a href="#">修改</a> <a href="#">删除</a>;
                    }
                }
                ]
            });

        });
    bodydiv ="table-responsive" style="padding: 10px 30px"table id="table" class="table text-nowrap"tabledivhtml>

3.3.  演示

重点演示几个查询

返回结果:

{
    "code": 200,"success": true,"msg": "SUCCESS","data": {
        "pageNumber": 1,"pageSize": 10,"totalCount": 2,"pageList": [
            {
                "id": 2,"name": "倚天屠龙记(全四册)","author": "金庸","category": 10011,"price": 70.4,"sellReason": "武林至尊,宝刀屠龙,号令天下,莫敢不从。","sellTime": "2018-11-11","status": 1
            },{
                "id": 3,"name": "神雕侠侣","price": 70,"sellReason": "风陵渡口初相遇,一见杨过误终身","status": 1
            }
        ]
    }
}

上面的查询对应的Elasticsearch DSL是这样的:

{
    "from":0,"size":10,"query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "author":{
                            "query":"金庸","operator":"OR","prefix_length":0,"max_expansions":50,"fuzzy_transpositions":true,"lenient":false,"zero_terms_query":"NONE","auto_generate_synonyms_phrase_query":true,"boost":1
                        }
                    }
                },{
                    "term":{
                        "status":{
                            "value":1,{
                    "bool":{
                        "should":[
                            {
                                "term":{
                                    "category":{
                                        "value":10010,"boost":1
                                    }
                                }
                            },{
                                "term":{
                                    "category":{
                                        "value":10011,{
                                "term":{
                                    "category":{
                                        "value":10012,"boost":1
                                    }
                                }
                            }
                        ],"adjust_pure_negative":true,"boost":1
                    }
                }
            ],"boost":1
        }
    },"sort":[
        {
            "id":{
                "order":"asc"
            }
        }
    ]
}

3.4.  工程结构

 

4.  参考

https://github.com/medcl/elasticsearch-analysis-ik

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html

https://bootstrap-table.wenzhixin.net.cn/documentation/

 

5.  其它相关

Elasticsearch 分词器

Elasticsearch Document

Elasticsearch Search API

Elasticsearch查询

Elasticsearch Mapping

SpringBoot+Elasticsearch

ELK快速搭建日志平台

 

相关文章

文章浏览阅读774次,点赞24次,收藏16次。typescript项目中我...
文章浏览阅读784次。react router redux antd eslint pretti...
文章浏览阅读3.9k次,点赞5次,收藏11次。需要删除.security...
文章浏览阅读1.2k次,点赞23次,收藏24次。Centos 8 安装es_...
文章浏览阅读3.2k次。设置完之后,数据会⾃动同步到其他节点...
文章浏览阅读1.9k次,点赞2次,收藏7次。针对多数据源写入的...