Java操作方案

直接使用 HTTP

直接使用 HTTP 请求,去操作 Es。HTTP 请求工具,可以使用 Java 自带的 HttpUrlConnection,也可以使用一些 HTTP 请求库,例如 HttpClientOKHttp、Spring 中的 RestTemplate 都可以。这种方式有一个弊端,就是要自己组装请求参数,自己去解析响应的 JSON。

Low Level REST Client

用于 Es 的官方的低级客户端。这种方式允许通过 HTTP 与 Es 集群进行通信,但是请求时候的 JSON 参数和响应的 JSON 参数交给用户去处理。这种方式好处就是兼容所有的 Es 版本。但是就是数据处理比较麻烦。

High Level REST Client

用户 Es 的官方的高级客户端。这种方式允许通过 HTTP 与 Es 集群进行通信,它是基于 Low Level REST Client,但是提供了很多 API,开发者不需要自己去组装参数,也不需要自己去解析响应 JSON 。这种方式使用起来更加直接。但是需要注意,这种方式,所使用的依赖库的版本要和 Es 对应。

TransportClient

TransportClient 在 Es7 中已经被弃用,在 Es8 中将被完全删除。

下面我们将通过 High Level REST Client 作为演示。

添加 Maven 依赖,注意:使用该依赖需要确保版本跟 elasticsearch 版本一致

<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.4.2</version>
</dependency>

注意:如果你是在 SpringBoot 环境下学习测试,必须查看版本是否正确。例如下面这种情况,既有7.6.2又有7.4.2,原因就是 SpringBoot 帮我们管理了一个 elasticsearch 版本,只需要在 pom 中指定版本,覆盖掉 SpringBoot 的配置即可。

创建配置类,这里我就起名 LaketradingElasticsearchConfig

@Configuration
public class LaketradingElasticsearchConfig {

  public static final RequestOptions COMMON_OPTIONS;
  static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
    //builder.addHeader("Authorization", "Bearer " + TOKEN);
    //builder.setHttpAsyncResponseConsumerFactory(
    //        new HttpAsyncResponseConsumerFactory
    //                .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
    COMMON_OPTIONS = builder.build();
  }

  @Bean
  public RestHighLevelClient esRestClient(){
    return new RestHighLevelClient(
      RestClient.builder(
        new HttpHost("192.168.56.10", 9200, "http")));
  }
}

官方的API文档地址:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-supported-apis.html

建议读一遍官方文档,了解一个大概,用时查阅即可。

举例测试

接下来我们对之前在高级检索博客中的一些例子,在 java 中用代码实现。

首先是一个匹配的问题,我们对 "address" = "mill lane" 的结果进行筛选。

@RunWith(SpringRunner.class)
@SpringBootTest
public class LaketradingSearchApplicationTests {

  @Autowired
  private RestHighLevelClient client;

  @Test
  public void searchData() throws IOException {
    // 创建索引请求
    SearchRequest searchRequest = new SearchRequest();
    // 指定索引
    searchRequest.indices("bank");
    // 构造索引条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address","mill lane"));
    System.out.println("检索条件:" + sourceBuilder.toString());
    searchRequest.source(sourceBuilder);
    // 执行
    SearchResponse searchResponse = client.search(searchRequest, LaketradingElasticsearchConfig.COMMON_OPTIONS);
    System.out.println(searchResponse.toString());
  }
}

在控制台打印出来的结果可以看出跟我们之前测试一模一样。接下来我们去测试聚合检索。

结果:31岁的人有61个,他们的平均工资为28312。

"aggregations" : {
  "ageAgg" : {
    "doc_count_error_upper_bound" : 0,
    "sum_other_doc_count" : 463,
    "buckets" : [
      {
        "key" : 31,
        "doc_count" : 61,
        "balanceAvg" : {
          "value" : 28312.918032786885
        }
      },
      ...
    ]
  }
}

java代码

@Test
public void searchData() throws IOException {
  // 创建索引请求
  SearchRequest searchRequest = new SearchRequest();
  // 指定索引
  searchRequest.indices("bank");
  // 构造索引条件
  SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
  // 聚合条件工具类
  AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
  TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").subAggregation(balanceAvg);
  sourceBuilder.aggregation(ageAgg);
  // 因为这次是聚合检索,我们就不看查询结果了,直接看聚合结果。
  sourceBuilder.size(0);
  System.out.println("检索条件:" + sourceBuilder.toString());
  searchRequest.source(sourceBuilder);
  // 执行
  SearchResponse searchResponse = client.search(searchRequest, LaketradingElasticsearchConfig.COMMON_OPTIONS);
  System.out.println(searchResponse.toString());
}

当然我们也可以在年龄聚合外来计算平均工资,也就是计算所有人的平均工资,那就把 balanceAvg 跟 ageAgg 同级即可。

AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age");
sourceBuilder.aggregation(ageAgg).aggregation(balanceAvg);

接下来我们要对命中记录进行分析,在官方文档中有一个 searchResponse.getHits() 方法。

文档位置:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-search.html

完整的代码测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class LaketradingSearchApplicationTests {

  @Autowired
  private RestHighLevelClient client;

  @Test
  public void search2Data() throws IOException {
    // 创建索引请求
    SearchRequest searchRequest = new SearchRequest();
    // 指定索引
    searchRequest.indices("bank");
    // 构造索引条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
    // 聚合条件工具类
    AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
    TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age");
    sourceBuilder.aggregation(ageAgg).aggregation(balanceAvg);
    searchRequest.source(sourceBuilder);
    // 执行
    SearchResponse searchResponse = client.search(searchRequest, LaketradingElasticsearchConfig.COMMON_OPTIONS);
    // 获取命中结果
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
      // 拿到完整结果字符串
      String sourceAsString = hit.getSourceAsString();
      // 转换成实体类
      Source source = JSON.parseObject(sourceAsString, Source.class);
      System.out.println("source:" + source );
    }
    // 获取聚合结果
    Aggregations aggregations = searchResponse.getAggregations();
    Terms age = aggregations.get("ageAgg");
    age.getBuckets().forEach(bucket -> System.out.println("年龄: "+ bucket.getKeyAsString() + ",人数:" + bucket.getDocCount()));
    Avg balance = aggregations.get("balanceAvg");
    System.out.println("平均薪资:" + balance.getValue());
  }
}

@Data
@ToString
class Source {
  private int account_number;
  private int balance;
  private String firstname;
  private String lastname;
  private int age;
  private String gender;
  private String address;
  private String employer;
  private String email;
  private String city;
  private String state;
}

结果

source:Source(account_number=970, balance=19648, firstname=Forbes, lastname=Wallace, age=28, gender=M, address=990 Mill Road, employer=Pheast, email=forbeswallace@pheast.com, city=Lopezo, state=AK)
source:Source(account_number=136, balance=45801, firstname=Winnie, lastname=Holland, age=38, gender=M, address=198 Mill Lane, employer=Neteria, email=winnieholland@neteria.com, city=Urie, state=IL)
source:Source(account_number=345, balance=9812, firstname=Parker, lastname=Hines, age=38, gender=M, address=715 Mill Avenue, employer=Baluba, email=parkerhines@baluba.com, city=Blackgum, state=KY)
source:Source(account_number=472, balance=25571, firstname=Lee, lastname=Long, age=32, gender=F, address=288 Mill Street, employer=Comverges, email=leelong@comverges.com, city=Movico, state=MT)
年龄: 38,人数:2
年龄: 28,人数:1
年龄: 32,人数:1
平均薪资:25208.0

参考资料:松哥公众号


Last modification:January 28, 2021
如果觉得我的文章对你有用,请随意赞赏