Elasticsearch Java API Client [8.17] 使用

es8出了个新的JavaClient,相较于HighLevelClient少了对es源代码的引用,更加的轻便了,这里记录下使用方式。

文档地址:Introduction | Elasticsearch Java API Client 8.17 | Elastic

本地运行的是es 8.6.2版本

JavaClient使用的是8.17

1、首先创建个java springboot项目

源码地址:https://github.com/a66245753/es-8-java-client.git

pom依赖文件如下

复制代码
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.david</groupId>
    <artifactId>es8-java-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>es8-java-client</name>
    <description>es8-java-client</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>8.17.3</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.0</version>
        </dependency>
        <!-- Lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.8</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.yml

数据库需要改为自己的

复制代码
spring:
  application:
    name: es8-java-client


  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/david?charset=utf8mb4&parseTime=True&loc=Local
    username: david
    password: david


mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

按照官网上声明EsClient 为spring bean

复制代码
@Configuration
public class EsClientConfig {

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // URL and API key
        String serverUrl = "http://localhost:9200";
        //        String apiKey = "VnVhQ2ZHY0JDZGJrU...";

        // Create the low-level client
        RestClient restClient = RestClient
                .builder(HttpHost.create(serverUrl))
//                .setDefaultHeaders(new Header[]{
//                        new BasicHeader("Authorization", "ApiKey " + apiKey)
//                })
                .build();

        // Create the transport with a Jackson mapper
        ElasticsearchTransport transport = new RestClientTransport(
                restClient, new JacksonJsonpMapper());

        // And create the API client
        return new ElasticsearchClient(transport);
    }
}

2、es client使用

class:com.david.coltroller.EsDocController

2.1 新增数据

有单个新增和批量新增,使用批量时最好把索引的更新频率设置一下

复制代码
/**
     * https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/indexing.html
     *
     * @param add
     * @return
     * @throws IOException
     */
    @PostMapping("/add")
    public ResponseEntity<EsResponseBody> add(@RequestBody EsDocAdd add) throws IOException {
        try {
            IndexResponse response = elasticsearchClient.index(i -> i
                    .index(add.getIndex())
                    // 不设置id,默认使用es生成的
                    // .id(product.getSku())
                    .document(add.getDoc())
            );
            EsResponseBody responseBody = new EsResponseBody(0, response.id(), null);
            return new ResponseEntity<>(responseBody, HttpStatus.OK);
        } catch (ElasticsearchException e) {
            EsResponseBody responseBody = new EsResponseBody(-1, null, e.error().toString());
            return new ResponseEntity<>(responseBody, HttpStatus.OK);
        }
    }

    /**
     * https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/indexing-bulk.html
     *
     * @param add
     * @return
     * @throws IOException
     */
    @PostMapping("/addBatch")
    public ResponseEntity<EsResponseBody> addBatch(@RequestBody EsDocAddBatch add) throws IOException {

        BulkRequest.Builder br = new BulkRequest.Builder();
        for (ObjectNode doc : add.getDocs()) {
            br.operations(op -> op
                    .index(idx -> idx
                            .index(add.getIndex())
                            .document(doc)
                    )
            );
        }
        try {
            BulkResponse result = elasticsearchClient.bulk(br.build());
            if (result.errors()) {
                List<EsDocBucketError> list = new ArrayList<>();
                int i = 0;
                for (BulkResponseItem item : result.items()) {
                    if (item.error() != null) {
                        list.add(new EsDocBucketError(item.error().reason(), add.getDocs().get(i)));
                    }
                    i++;
                }
                EsResponseBody responseBody = new EsResponseBody(-1, list, "失败");
                return new ResponseEntity<>(responseBody, HttpStatus.OK);
            }
            EsResponseBody responseBody = new EsResponseBody(0, null, null);
            return new ResponseEntity<>(responseBody, HttpStatus.OK);
        } catch (ElasticsearchException e) {
            EsResponseBody responseBody = new EsResponseBody(-1, null, e.error().toString());
            return new ResponseEntity<>(responseBody, HttpStatus.OK);
        }
    }

建议使用模版的方式创建索引,比如我下面的dsl语句规定了索引内字段的类型。

复制代码
PUT _index_template/ssp_ad_union_log_template
{
  // 匹配的索引模式
  "index_patterns": ["ssp_ad_union_log_*"], 
  "template": {
    "settings": {
      "number_of_shards": 1,       
      "number_of_replicas": 1,
      "refresh_interval":"1s"      
    },
    "mappings": {
      "dynamic": "strict",
      "properties": {
        "id": { "type": "long" },   
        "reqId": { "type": "keyword" },   
        "device": { "type": "keyword" },
        "platform": { "type": "byte" },
        "platformName":{"type":"keyword"},
        "clientType": { "type": "byte" },
        "myAppId":{"type":"keyword"},
        "deviceId":{"type":"keyword"},
        "adSiteGroupId": { "type": "long" },
        "adSiteId":{"type":"keyword"},
        "packagePath":{"type":"keyword"},
        "ecpm": { "type": "long" },
        "location":{"type":"geo_point"},
        "ip":{"type":"ip"},
        "cityId":{"type":"integer"},
        "cityName":{"type":"keyword"},
        "areaId":{"type":"integer"},
        "areaName":{"type":"keyword"},
        "provinceId":{"type":"integer"},
        "provinceName":{"type":"keyword"},
        "phoneBrand":{"type":"keyword"},
        "phoneBrandName":{"type":"keyword"},
        "phoneModel":{"type":"keyword"},
        "idfa":{"type":"keyword"},
        "bizType":{"type":"byte"},
        "sdkVersion":{"type":"keyword"},
        "network":{"type":"keyword"},
        "logTime":{"type":"date","format":"yyyy-MM-dd HH:mm:ss"},
        "createdAt":{"type":"date","format":"yyyy-MM-dd HH:mm:ss"}
      }
    },
    "aliases":{
      "ssp_ad_union_log":{}
    }
  }
}

2.2 根据es的objectId获取数据

复制代码
    /**
     * 根据es的objectId获取数据
     * @param index
     * @param id
     * @return
     * @throws IOException
     */
    @GetMapping("/get")
    public ResponseEntity<EsResponseBody> get(@RequestParam("index") String index, @RequestParam("id") String id) throws IOException {

        GetResponse<ObjectNode> response = elasticsearchClient.get(g -> g
                        .index(index)
                        .id(id),
                ObjectNode.class
        );
        if (response.found()) {
            EsResponseBody responseBody = new EsResponseBody(0, response.source(), null);
            return new ResponseEntity<>(responseBody, HttpStatus.OK);
        }
        EsResponseBody responseBody = new EsResponseBody(-1, null, "not found");
        return new ResponseEntity<>(responseBody, HttpStatus.OK);
    }

    /**
     * 根据es的多个objectId获取数据
     * @param params
     * @return
     * @throws IOException
     */
    @PostMapping("/getBatch")
    public ResponseEntity<EsResponseBody> getBatch(@RequestBody EsGetBatchParams params) throws IOException {

        MgetResponse<ObjectNode> response = elasticsearchClient.mget(g -> g
                        .index(params.getIndex())
                        .ids(params.getIdList()),
                ObjectNode.class
        );
        List<ObjectNode> list = new ArrayList<>();
        for (MultiGetResponseItem<ObjectNode> doc : response.docs()) {
            if (doc.isResult()) {
                list.add(doc.result().source());
            }
            if (doc.isFailure()) {
                System.out.println("isFailure doc" + doc.toString());
            }
        }
        EsResponseBody responseBody = new EsResponseBody(0, list, null);
        return new ResponseEntity<>(responseBody, HttpStatus.OK);
    }

2.3 查询分页

这里写了个通用查询

复制代码
/**
     * 查询分页
     * @param request
     * @return
     * @throws IOException
     */
    @PostMapping("/search")
    public ResponseEntity<EsResponseBody> search(@RequestBody EsSearchRequest request) throws IOException {
        // 构造查询过滤条件
        List<Query> list = buildQueryList(request);
        Query query = null;
        if (request.getLikeParams() != null && !request.getLikeParams().isEmpty()) {
            // 使用分值查询,分高的排前面
            query = new BoolQuery.Builder().must(list).build()._toQuery();
        } else {
            // 使用filter不算分查询
            query = new BoolQuery.Builder().filter(list).build()._toQuery();
        }
        // 创建 SearchRequest 并指定索引名称
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index(request.getIndexList())
                .query(query)
                .trackTotalHits(tth -> tth.enabled(true))
                .from((request.getPageIndex() - 1) * request.getPageSize())
                .size(request.getPageSize())
                .build();

        // 打印查询语句,方便排查问题
        System.out.println("search query: " + searchRequest.toString());

        // 执行搜索请求, 第二个参数可以换成自己的返回值对象
        SearchResponse<ObjectNode> response = elasticsearchClient.search(searchRequest, ObjectNode.class);
        List<ObjectNode> collect = response.hits().hits().stream().map(Hit::source).toList();
        EsSearchResult result = new EsSearchResult();
        result.setPageIndex(request.getPageIndex());
        result.setPageSize(request.getPageSize());
        result.setTotal(response.hits().total().value());
        result.setList(collect);
        EsResponseBody responseBody = new EsResponseBody(0, result, null);

        return new ResponseEntity<>(responseBody, HttpStatus.OK);
    }

/**
     * 组装请求参数
     *
     * @param request
     * @return
     */
    private List<Query> buildQueryList(EsSearchRequest request) {
        List<Query> list = new ArrayList<>();
        if (request.getEqualsParams() != null && !request.getEqualsParams().isEmpty()) {
            for (Map.Entry<String, Object> entry : request.getEqualsParams().entrySet()) {
                // 构建 TermQuery 对象
                Query query = new TermQuery.Builder()
                        .field(entry.getKey())
                        .value(entry.getValue().toString())
                        .build()._toQuery();
                list.add(query);
            }
        }
        if (request.getInParams() != null && !request.getInParams().isEmpty()) {
            for (EsInParams params : request.getInParams()) {
                // 构建 TermsQuery 对象
                Query query = new TermsQuery.Builder()
                        .field(params.getField())
                        .terms(t -> t.value(params.getValueList().stream().map(FieldValue::of).toList()))
                        .build()._toQuery();
                list.add(query);
            }
        }
        if (request.getLikeParams() != null && !request.getLikeParams().isEmpty()) {
            for (Map.Entry<String, String> entry : request.getLikeParams().entrySet()) {
                // 构建 MatchQuery 对象
                Query query = new MatchQuery.Builder()
                        .field(entry.getKey())
                        .query(entry.getValue())
                        .build()._toQuery();
                list.add(query);
            }
        }
        if (request.getRangeParams() != null && !request.getRangeParams().isEmpty()) {
            for (EsRangeParams rangeParam : request.getRangeParams()) {
                if (rangeParam.getFieldType() == 1) {
                    Query query = new NumberRangeQuery.Builder()
                            .field(rangeParam.getField())
                            .gte(Double.valueOf(rangeParam.getGte()))
                            .lte(Double.valueOf(rangeParam.getLte()))
                            .build()._toRangeQuery()._toQuery();
                    list.add(query);
                } else {
                    Query query = new DateRangeQuery.Builder()
                            .field(rangeParam.getField())
                            .gte(rangeParam.getGte())
                            .lte(rangeParam.getLte())
                            .build()._toRangeQuery()._toQuery();
                    list.add(query);
                }
            }
        }
        return list;
    }

2.4 聚合数据(分组统计)

聚合没有太多的通用性,按照自己业务的需求写就可以。

下面是按照了城市维度分组,统计了每个城市下面业务类型数量、网络类型数量、sdk版本数量、平台数量、手机型号数量,以及最后一个是带条件过滤在统计的数据平均值。

复制代码
    @PostMapping("/aggs")
    public ResponseEntity<EsResponseBody> aggs(@RequestBody EsSearchRequest request) throws IOException {

        // 构造查询过滤条件
        List<Query> list = buildQueryList(request);

        Query query = new BoolQuery.Builder().filter(list).build()._toQuery();

        // 创建 SearchRequest 并指定索引名称和其他参数
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index("ssp_ad_union_log")
                .trackTotalHits(tth -> tth.enabled(true))
                .from(0)
                .size(1)
                .query(query)
                .aggregations("cityName_bucket",
                        // 一级聚合
                        a -> a.terms(t -> t.field("cityName").size(10))
                                // 二级聚合
                                .aggregations("bizType_bucket", aa -> aa.terms(ts -> ts.field("bizType").size(10)))
                                .aggregations("network_bucket", aa -> aa.terms(ts -> ts.field("network").size(10)))
                                .aggregations("sdkVersion_bucket", aa -> aa.terms(t3 -> t3.field("sdkVersion").size(10)))
                                .aggregations("platformName_bucket", aa -> aa.terms(t4 -> t4.field("platformName").size(10)))
                                .aggregations("phoneBrandName_bucket", aa -> aa.terms(t5 -> t5.field("phoneBrandName").size(10)))
                                // 带条件二级聚合
                                .aggregations("ecpmAvg_bucket", aa -> aa.filter(f6 -> f6.term(c -> c.field("bizType").value(2))).aggregations("ecpmAvg_bucket", ss -> ss.avg(avg -> avg.field("ecpm"))))
                )
                .build();

        // 打印查询语句,方便排查问题
        System.out.println("search query: " + searchRequest.toString());

        // 执行搜索请求
        SearchResponse<ObjectNode> response = elasticsearchClient.search(searchRequest, ObjectNode.class);
        // 处理结果
        System.out.println(response.aggregations().toString());
        List<StringTermsBucket> buckets = response.aggregations()
                .get("cityName_bucket")
                .sterms()
                .buckets()
                .array();
        EsResponseBody responseBody = new EsResponseBody(0, response.aggregations().toString(), null);
        return new ResponseEntity<>(responseBody, HttpStatus.OK);
    }
相关推荐
linmengmeng_13145 分钟前
【总结】MyBatis-Plus批量插入与清表的正确姿势
java·spring boot·mysql·mybatis
木井巳10 分钟前
【记忆化搜索】不同路径
java·算法·leetcode·深度优先·剪枝·推荐算法
Sam_Deep_Thinking11 分钟前
new Thread()之后发生了什么?
java·后端·面试·程序员
泡海椒11 分钟前
规则引擎对比:JQuick-Java vs Drools 轻量场景选型对比
java·开发语言
Regentsoft丽晶软件18 分钟前
即时零售退货的库存归属、质检标准和退款时效,系统如何做到自动闭环
大数据·经验分享·数据库架构
动恰客流统计19 分钟前
线下零售数字化浪潮下,客流统计的3个核心发展趋势
大数据·前端·人工智能
wno70431 分钟前
Spring Security短信验证码登录
java·python·spring
步行cgn38 分钟前
Spring util 命名空间详解
java·后端·spring
adinnet20261 小时前
Neo4j 凭什么叫原生图数据库?Cypher 查询能做什么
大数据·人工智能
古法安卓1 小时前
Android-休眠唤醒后onLocationChanged没有数据问题排查
android·java·android studio