使用Java操作Elasticsearch(Elasticsearch的java api使用)

时间:2022-06-01 20:46:16

1、Elasticsearch是基于Lucene开发的一个分布式全文检索框架,向Elasticsearch中存储和从Elasticsearch中查询,格式是json。

索引index,相当于数据库中的database。

类型type相当于数据库中的table。

主键id相当于数据库中记录的主键,是唯一的。

向Elasticsearch中存储数据,其实就是向es中的index下面的type中存储json类型的数据。

2、Elasticsearch是RestFul风格的api,通过http的请求形式(注意,参数是url拼接还是请求的json形式哦),发送请求,对Elasticsearch进行操作。
查询,请求方式应该是get。删除,请求方式应该是delete。添加,请求方式应该是put/post。修改,请求方式应该是put/post。
RESTFul接口url的格式:http://ip:port/<index>/<type>/<[id]>。其中index、type是必须提供的。id是可以选择的,不提供es会自动生成,index、type将信息进行分层,利于管理。

3、如何使用java连接Elasticsearch。由于使用的是maven项目,pom.xml的依赖如下所示:

 <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.</modelVersion>
<groupId>com.bie</groupId>
<artifactId>elasticsearch-hello</artifactId>
<version>0.0.-SNAPSHOT</version> <properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<encoding>UTF-</encoding>
</properties> <dependencies>
<!-- elasticsearch的客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.4.</version>
</dependency>
<!-- elasticsearch依赖2.x的log4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.</version>
</dependency>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies> </project>

使用查询的方式,先简单测试一下是否连通es集群,和对比查询的数据是否一致。

 package com.bie.elasticsearch;

 import java.net.InetAddress;

 import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient; /**
*
* @author biehl
*
*/
public class HelloElasticsearch { public static void main(String[] args) {
try {
// 设置集群名称biehl01,Settings设置es的集群名称,使用的设计模式,链式设计模式、build设计模式。
Settings settings = Settings.builder().put("cluster.name", "biehl01").build();
// 读取es集群中的数据,创建client。
@SuppressWarnings("resource")
TransportClient client = new PreBuiltTransportClient(settings).addTransportAddresses(
// 用java访问ES用的端口是9300。es的9200是restful的请求端口号
// 由于我使用的是伪集群,所以就配置了一台机器,如果是集群方式,将竞选主节点的加进来即可。
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"), ));
// 搜索数据(.actionGet()方法是同步的,没有返回就等待)
// 方式是先去索引里面查询出索引数据,再去文档里面查询出数据。
GetResponse response = client.prepareGet("news", "fulltext", "").execute().actionGet();
// 输出结果
System.out.println(response);
// 关闭client
client.close();
} catch (Exception e) {
e.printStackTrace();
} } }

查询的结果如下所示:

使用Java操作Elasticsearch(Elasticsearch的java api使用)

4、如何使用java api创建索引Index、类型Type、以及指定字段,是否创建索引,是否存储,是否即分词,又建立索引(analyzed)、是否建索引不分词(not_analyzed)等等。

 package com.bie.elasticsearch;

 import java.io.IOException;
import java.net.InetAddress;
import java.util.HashMap; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
import org.elasticsearch.client.AdminClient;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.Before;
import org.junit.Test; /**
*
* @author biehl
*
*/
public class AdminAPI { private TransportClient client = null; // 在所有的测试方法之前执行
@SuppressWarnings("resource")
@Before
public void init() throws Exception {
// 设置集群名称biehl01
Settings settings = Settings.builder().put("cluster.name", "biehl01")
// 自动感知的功能(可以通过当前指定的节点获取所有es节点的信息)
.put("client.transport.sniff", true).build();
// 创建client
client = new PreBuiltTransportClient(settings).addTransportAddresses(
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
// 建议指定2个及其以上的节点。
new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"), ));
} /**
*
* AdminClient创建索引,并配置一些参数,用来指定一些映射关系等等
*
* 这里创建一个索引Index,并且指定分区、副本的数量
*
*/
@Test
public void createIndexWithSettings() {
// 获取Admin的API
AdminClient admin = client.admin();
// 使用Admin API对索引进行操作
IndicesAdminClient indices = admin.indices();
// 准备创建索引
indices.prepareCreate("food")
// 配置索引参数
.setSettings(
// 参数配置器
Settings.builder()// 指定索引分区的数量。shards分区
.put("index.number_of_shards", )
// 指定索引副本的数量(注意:不包括本身,如果设置数据存储副本为1,实际上数据存储了2份)
// replicas副本
.put("index.number_of_replicas", ))
// 真正执行
.get();
} /**
* 你可以通过dynamic设置来控制这一行为,它能够接受以下的选项: true:默认值。
*
* 动态添加字段 false:忽略新字段
*
* strict:如果碰到陌生字段,抛出异常
*
* 给索引添加mapping信息(给表添加schema信息)
*
* @throws IOException
*/
@Test
public void elasticsearchSettingsMappings() throws IOException {
// 1:settings
HashMap<String, Object> settings_map = new HashMap<String, Object>();
// shards分区的数量4
settings_map.put("number_of_shards", );
// 副本的数量1
settings_map.put("number_of_replicas", ); // 2:mappings(映射、schema)
// field("dynamic", "true")含义是动态字段
XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("dynamic", "true")
// 设置type中的属性
.startObject("properties")
// id属性
.startObject("id")
// 类型是integer
.field("type", "integer")
// 不分词,但是建索引
.field("index", "not_analyzed")
// 在文档中存储
.field("store", "yes").endObject()
// name属性
.startObject("name")
// string类型
.field("type", "string")
// 在文档中存储
.field("store", "yes")
// 建立索引
.field("index", "analyzed")
// 使用ik_smart进行分词
.field("analyzer", "ik_smart").endObject().endObject().endObject(); CreateIndexRequestBuilder prepareCreate = client.admin().indices().prepareCreate("computer");
// 管理索引(user_info)然后关联type(user)
prepareCreate.setSettings(settings_map).addMapping("xiaomi", builder).get();
} /**
* index这个属性,no代表不建索引
*
* not_analyzed,建索引不分词
*
* analyzed 即分词,又建立索引
*
* expected [no],[not_analyzed] or [analyzed]。即可以选择三者任意一个值
*
* @throws IOException
*/ @Test
public void elasticsearchSettingsPlayerMappings() throws IOException {
// 1:settings
HashMap<String, Object> settings_map = new HashMap<String, Object>();
// 分区的数量4
settings_map.put("number_of_shards", );
// 副本的数量1
settings_map.put("number_of_replicas", ); // 2:mappings
XContentBuilder builder = XContentFactory.jsonBuilder().startObject()//
.field("dynamic", "true").startObject("properties")
// 在文档中存储、
.startObject("id").field("type", "integer").field("store", "yes").endObject()
// 不分词,但是建索引、
.startObject("name").field("type", "string").field("index", "not_analyzed").endObject()
//
.startObject("age").field("type", "integer").endObject()
//
.startObject("salary").field("type", "integer").endObject()
// 不分词,但是建索引、
.startObject("team").field("type", "string").field("index", "not_analyzed").endObject()
// 不分词,但是建索引、
.startObject("position").field("type", "string").field("index", "not_analyzed").endObject()
// 即分词,又建立索引、
.startObject("description").field("type", "string").field("store", "no").field("index", "analyzed")
.field("analyzer", "ik_smart").endObject()
// 即分词,又建立索引、在文档中存储、
.startObject("addr").field("type", "string").field("store", "yes").field("index", "analyzed")
.field("analyzer", "ik_smart").endObject() .endObject() .endObject(); CreateIndexRequestBuilder prepareCreate = client.admin().indices().prepareCreate("player");
prepareCreate.setSettings(settings_map).addMapping("basketball", builder).get();
}
}

5、使用java api操作Elasticsearch的增删改查以及复杂查询(聚合查询,可以进行分组统计数量,分组统计最大值,分组统计平均值,等等统计)。

 package com.bie.elasticsearch;

 import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.rangeQuery; import java.io.IOException;
import java.net.InetAddress;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.bulk.byscroll.BulkByScrollResponse;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.DeleteByQueryAction;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg;
import org.elasticsearch.search.aggregations.metrics.max.InternalMax;
import org.elasticsearch.search.aggregations.metrics.max.MaxAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.sum.InternalSum;
import org.elasticsearch.search.aggregations.metrics.sum.SumAggregationBuilder;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.Before;
import org.junit.Test; /**
*
* @author biehl
*
*/
public class ElasticsearchCRUD { private TransportClient client = null; @SuppressWarnings("resource")
@Before
public void init() throws Exception {
// 设置集群名称biehl01
Settings settings = Settings.builder().put("cluster.name", "biehl01")
// 自动感知的功能(可以通过当前指定的节点获取所有es节点的信息)
.put("client.transport.sniff", true).build();
// 创建client
client = new PreBuiltTransportClient(settings).addTransportAddresses(
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
// new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"),
// 9300),
// 建议指定2个及其以上的节点。
new InetSocketTransportAddress(InetAddress.getByName("192.168.110.133"), ));
} /**
* 创建一个Index索引、Type类型、以及id。
*
* 然后插入类型里面的数据。
*
* @throws IOException
*/
@Test
public void elasticsearchCreate() throws IOException {
IndexResponse response = client.prepareIndex("people", "student", "")
.setSource(jsonBuilder().startObject().field("username", "王五五").field("sex", "男")
.field("birthday", new Date()).field("age", ).field("message", "trying out Elasticsearch")
.endObject())
.get();
System.out.println(response.toString());
} /**
* 查找一条索引Index里面的类型Type里面的id的所有信息
*
* @throws IOException
*/
@Test
public void elasticsearchGet() throws IOException {
GetResponse response = client.prepareGet("people", "student", "").get();
System.out.println(response.getSourceAsString());
} /**
* 查找多条
*
* 索引Index里面的类型Type里面的多个id的所有信息
*
* @throws IOException
*/
@Test
public void elasticsearchMultiGet() throws IOException {
// 查询出多个索引Index多个类型Type的多个id的所有信息
MultiGetResponse multiGetItemResponses = client.prepareMultiGet().add("people", "student", "")
.add("people", "student", "", "").add("people", "teacher", "").add("news", "fulltext", "").get();
// 将查询出的结果遍历输出
for (MultiGetItemResponse itemResponse : multiGetItemResponses) {
// 将每一个查询出的结果遍历输出
GetResponse response = itemResponse.getResponse();
// 判断如果存在就进行遍历输出
if (response.isExists()) {
String json = response.getSourceAsString();
System.out.println(json);
}
}
} /**
* 修改指定的索引Index里面的类型Type的id的信息
*
* @throws Exception
*/
@Test
public void elasticsearchUpdate() throws Exception {
// 创建一个更新的请求对象
UpdateRequest updateRequest = new UpdateRequest();
// 指定索引Index
updateRequest.index("people");
// 指定类型Type
updateRequest.type("student");
// 指定id的值
updateRequest.id("");
// 设置修改的字段信息
updateRequest.doc(jsonBuilder().startObject().field("username", "王五五").endObject());
// 开始进行修改,并且返回响应信息
UpdateResponse updateResponse = client.update(updateRequest).get();
// 打印输出响应的信息
System.out.println(updateResponse.toString());
} /**
* 删除指定的索引Index里面的类型Type的id的信息
*/
@Test
public void elasticsearchDelete() {
// 指定删除的id信息,并且给出响应结果
// prepareDelete(String index, String type, String id);
DeleteResponse response = client.prepareDelete("people", "student", "").get();
// 打印输出的响应信息
System.out.println(response);
} /**
* 根据查询条件进行删除数据
*
*
*/
@Test
public void elasticsearchDeleteByQuery() {
BulkByScrollResponse response = DeleteByQueryAction.INSTANCE.newRequestBuilder(client)
// 指定查询条件,matchQuery是name的值text里面包括了这个内容就进行删除。默认使用标准分词器。
.filter(QueryBuilders.matchQuery("username", "王五五"))
// 指定索引名称
.source("people").get();
// 获取到删除的个数
long deleted = response.getDeleted();
// 打印输出删除的个数
System.out.println(deleted);
} /**
* 异步删除
*
* 监听,如果真正删除以后进行回调,打印输出删除确认的消息。
*/
@Test
public void elasticsearchDeleteByQueryAsync() {
DeleteByQueryAction.INSTANCE.newRequestBuilder(client).filter(QueryBuilders.matchQuery("sex", "男"))
.source("people").execute(new ActionListener<BulkByScrollResponse>() { // 删除以后的方法回调
@Override
public void onResponse(BulkByScrollResponse response) {
// 返回删除的个数
long deleted = response.getDeleted();
System.out.println("数据删除完毕!");
// 打印删除的个数
System.out.println("数据删除的个数: " + deleted);
} @Override
public void onFailure(Exception e) {
// 失败打印异常信息
e.printStackTrace();
}
}); // 先打印输出,正常执行完毕。再执行异步监听删除数据。
try {
System.out.println("异步删除操作!");
// 休眠10秒钟,避免主线程里面结束,子线程无法进行结果输出
Thread.sleep();
} catch (Exception e) {
e.printStackTrace();
}
} /**
*
* 按照范围进行查找。
*
*/
@Test
public void elasticsearchRange() {
// includeLower(true).includeUpper(false)含义是包含前面,不包含后面的
// [21, 24)
QueryBuilder qb = rangeQuery("age").from().to().includeLower(true).includeUpper(false);
// 将查询条件传递进去,并将查询结果进行返回。
SearchResponse response = client.prepareSearch("people").setQuery(qb).get();
System.out.println(response);
} /**
*
* 向指定索引index里面的类型Type的id的信息
*
* @throws IOException
*/
@Test
public void elasticsearchAddPlayer() throws IOException {
//
IndexResponse response = client.prepareIndex("player", "basketball", "") .setSource(jsonBuilder().startObject() .field("name", "安其拉") .field("age", ) .field("salary", ) .field("team", "啦啦队 team") .field("position", "打中锋") .field("description", "跪族蓝孩") .endObject())
.get(); System.out.println(response);
} /**
*
*
* select team, count(*) as team_count from player group by team;
*
* team_counts是别名称。
*/
@Test
public void elasticsearchAgg1() {
// 指定索引和type
SearchRequestBuilder builder = client.prepareSearch("player").setTypes("basketball");
// 按team分组然后聚合,但是并没有指定聚合函数。
// team_count是别名称
TermsAggregationBuilder teamAgg = AggregationBuilders.terms("team_count").field("team");
// 添加聚合器
builder.addAggregation(teamAgg);
// 触发
SearchResponse response = builder.execute().actionGet();
// System.out.println(response);
// 将返回的结果放入到一个map中
Map<String, Aggregation> aggMap = response.getAggregations().getAsMap();
// 遍历打印输出
Set<String> keys = aggMap.keySet();
for (String key : keys) {
System.out.println("key: " + key);
} System.out.println(""); // //取出聚合属性
StringTerms terms = (StringTerms) aggMap.get("team_count"); // //依次迭代出分组聚合数据
for (Terms.Bucket bucket : terms.getBuckets()) {
// 分组的名字
String team = (String) bucket.getKey();
// count,分组后一个组有多少数据
long count = bucket.getDocCount();
System.out.println(team + ": " + count);
} System.out.println(""); // 使用Iterator进行遍历迭代
Iterator<Terms.Bucket> teamBucketIt = terms.getBuckets().iterator();
while (teamBucketIt.hasNext()) {
Terms.Bucket bucket = teamBucketIt.next();
// 获取到分组后每组的组名称
String team = (String) bucket.getKey();
// 获取到分组后的每组数量
long count = bucket.getDocCount();
// 打印输出
System.out.println(team + ": " + count);
}
} /**
*
* select
*
* team, position, count(*) as pos_count
*
* from
*
* player
*
* group by
*
* team,position;
*
*
*/
@Test
public void elasticsearchAgg2() {
SearchRequestBuilder builder = client.prepareSearch("player").setTypes("basketball");
// 指定别名和分组的字段
TermsAggregationBuilder teamAgg = AggregationBuilders.terms("team_name").field("team");
TermsAggregationBuilder posAgg = AggregationBuilders.terms("pos_count").field("position");
// 添加两个聚合构建器。先按照team分组,再按照position分组。
builder.addAggregation(teamAgg.subAggregation(posAgg));
// 执行查询
SearchResponse response = builder.execute().actionGet();
// 将查询结果放入map中
Map<String, Aggregation> aggMap = response.getAggregations().getAsMap();
// 根据属性名到map中查找
StringTerms teams = (StringTerms) aggMap.get("team_name");
// 循环查找结果
for (Terms.Bucket teamBucket : teams.getBuckets()) {
// 先按球队进行分组
String team = (String) teamBucket.getKey();
Map<String, Aggregation> subAggMap = teamBucket.getAggregations().getAsMap();
StringTerms positions = (StringTerms) subAggMap.get("pos_count");
// 因为一个球队有很多位置,那么还要依次拿出位置信息
for (Terms.Bucket posBucket : positions.getBuckets()) {
// 拿到位置的名字
String pos = (String) posBucket.getKey();
// 拿出该位置的数量
long docCount = posBucket.getDocCount();
// 打印球队,位置,人数
System.out.println(team + ": " + pos + ": " + docCount);
}
} } /**
* select team, max(age) as max_age from player group by team;
*/
@Test
public void elasticsearchAgg3() {
SearchRequestBuilder builder = client.prepareSearch("player").setTypes("basketball");
// 指定安球队进行分组
TermsAggregationBuilder teamAgg = AggregationBuilders.terms("team_name").field("team");
// 指定分组求最大值
MaxAggregationBuilder maxAgg = AggregationBuilders.max("max_age").field("age");
// 分组后求最大值
builder.addAggregation(teamAgg.subAggregation(maxAgg));
// 查询
SearchResponse response = builder.execute().actionGet();
Map<String, Aggregation> aggMap = response.getAggregations().getAsMap();
// 根据team属性,获取map中的内容
StringTerms teams = (StringTerms) aggMap.get("team_name");
for (Terms.Bucket teamBucket : teams.getBuckets()) {
// 分组的属性名
String team = (String) teamBucket.getKey();
// 在将聚合后取最大值的内容取出来放到map中
Map<String, Aggregation> subAggMap = teamBucket.getAggregations().getAsMap();
// 取分组后的最大值
InternalMax ages = (InternalMax) subAggMap.get("max_age");
// 获取到年龄的值
double max = ages.getValue();
// 打印输出值
System.out.println(team + ": " + max);
}
} /**
* select team, avg(age) as avg_age, sum(salary) as total_salary from player
* group by team;
*/
@Test
public void elasticsearchAgg4() {
SearchRequestBuilder builder = client.prepareSearch("player").setTypes("basketball");
// 指定分组字段
TermsAggregationBuilder termsAgg = AggregationBuilders.terms("team_name").field("team");
// 指定聚合函数是求平均数据
AvgAggregationBuilder avgAgg = AggregationBuilders.avg("avg_age").field("age");
// 指定另外一个聚合函数是求和
SumAggregationBuilder sumAgg = AggregationBuilders.sum("total_salary").field("salary");
// 分组的聚合器关联了两个聚合函数
builder.addAggregation(termsAgg.subAggregation(avgAgg).subAggregation(sumAgg));
// 查询
SearchResponse response = builder.execute().actionGet();
Map<String, Aggregation> aggMap = response.getAggregations().getAsMap();
// 按分组的名字取出数据
StringTerms teams = (StringTerms) aggMap.get("team_name");
for (Terms.Bucket teamBucket : teams.getBuckets()) {
// 获取球队名字
String team = (String) teamBucket.getKey();
Map<String, Aggregation> subAggMap = teamBucket.getAggregations().getAsMap();
// 根据别名取出平均年龄
InternalAvg avgAge = (InternalAvg) subAggMap.get("avg_age");
// 根据别名取出薪水总和
InternalSum totalSalary = (InternalSum) subAggMap.get("total_salary");
double avgAgeValue = avgAge.getValue();
double totalSalaryValue = totalSalary.getValue();
System.out.println(team + ": " + avgAgeValue + ": " + totalSalaryValue);
}
} /**
* select team, sum(salary) as total_salary from player group by team order by
* total_salary desc;
*/
@Test
public void elasticsearchAgg5() {
SearchRequestBuilder builder = client.prepareSearch("player").setTypes("basketball");
// 按team进行分组,然后指定排序规则
TermsAggregationBuilder termsAgg = AggregationBuilders.terms("team_name").field("team")
.order(Terms.Order.aggregation("total_salary ", true));
// 指定一个聚合函数是求和
SumAggregationBuilder sumAgg = AggregationBuilders.sum("total_salary").field("salary");
// 添加两个聚合构建器。先按照team分组,再按照salary求和。
builder.addAggregation(termsAgg.subAggregation(sumAgg));
// 查询
SearchResponse response = builder.execute().actionGet();
// 将查询结果放入map中
Map<String, Aggregation> aggMap = response.getAggregations().getAsMap();
// 从查询结果中获取到team_name的信息
StringTerms teams = (StringTerms) aggMap.get("team_name");
// 开始遍历获取到的信息
for (Terms.Bucket teamBucket : teams.getBuckets()) {
// 获取到key的值
String team = (String) teamBucket.getKey();
// 获取到求和的值
Map<String, Aggregation> subAggMap = teamBucket.getAggregations().getAsMap();
// 获取到求和的值的信息
InternalSum totalSalary = (InternalSum) subAggMap.get("total_salary");
// 获取到求和的值
double totalSalaryValue = totalSalary.getValue();
// 打印输出信息
System.out.println(team + " " + totalSalaryValue);
}
} }

执行效果,自己可以分别进行测试。由于测试都写了说明,这里就不一一进行测试打印效果了。请自行练习使用即可。

aaarticlea/jpeg;base64,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" alt="" />

作者:别先生

博客园:https://www.cnblogs.com/biehongli/

如果您想及时得到个人撰写文章以及著作的消息推送,可以扫描上方二维码,关注个人公众号哦。