简介
说明
本文用实例来介绍如何使用Spring Data Elasticsearch的ElasticsearchRestTemplate来操作ES。包括:索引的增删等、文档的增删改查、文档的动态查询(或者说:多条件查询、复杂查询)。
动态查询的含义:查询条件有多个时,某个查询条件可能有也可能没有,这时就需要手动判断,如果为空则不拼装搜索条件。
本文用博客的业务进行示例。索引名:blog,里边有标题、内容、作者、创建时间等。
官网
为什么用ElasticsearchRestTemplate
在Spring Data Elasticsearch4.0之后,ElasticsearchRepository里边大部分搜索方法都已经被标记为废弃,注释中让我们用两种方法来操作:
- 在方法上使用@Query自定义DSL。
- 使用 ElasticsearchOperations来操作。
使用@Query自定义DSL不支持动态查询,其用法见:Spring Data Elasticsearch-使用实例 – 自学精灵
ElasticsearchOperations支持动态查询,ElasticsearchRestTemplate是ElasticsearchOperations的实现类。
公共代码
依赖及配置
依赖
主要是spring-boot-starter-data-elasticsearch这个依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
配置
spring: elasticsearch: rest: # 如果是集群,用逗号隔开 uris: http://127.0.0.1:9200 # username: xxx # password: yyy # connection-timeout: 1 # read-timeout: 30 # 上边是客户端High Level REST Client的配置,推荐使用。 # 下边是reactive客户端的配置,非官方,不推荐。 # data: # elasticsearch: # client: # reactive: # endpoints: 127.0.0.1:9200 # username: xxx # password: yyy # connection-timeout: 1 # socket-timeout: 30 # use-ssl: false
整个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 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>2.3.12.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo_spring-data-elasticsearch</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo_spring-data-elasticsearch</name> <description>demo_spring-data-elasticsearch</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
索引结构及数据
索引结构
http://localhost:9200/
PUT blog
{ "mappings": { "properties": { "id":{ "type":"long" }, "title": { "type": "text" }, "content": { "type": "text" }, "author":{ "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "category":{ "type": "keyword" }, "createTime": { "type": "date", "format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis" }, "updateTime": { "type": "date", "format":"yyyy-MM-dd HH:mm:ss.SSS||yyyy-MM-dd'T'HH:mm:ss.SSS||yyyy-MM-dd HH:mm:ss||epoch_millis" }, "status":{ "type":"integer" }, "serialNum": { "type": "keyword" } } } }
数据
- 每个文档必须独占一行,不能换行。
- 此命令要放到postman中去执行,如果用head执行会失败
http://localhost:9200/
POST _bulk
{"index":{"_index":"blog","_id":1}} {"blogId":1,"title":"Spring Data ElasticSearch学习教程1","content":"这是批量添加的文档1","author":"Iron Man","category":"ElasticSearch","status":1,"serialNum":"1","createTime":"2021-10-10 11:52:01.249","updateTime":null} {"index":{"_index":"blog","_id":2}} {"blogId":2,"title":"Spring Data ElasticSearch学习教程2","content":"这是批量添加的文档2","author":"Iron Man","category":"ElasticSearch","status":1,"serialNum":"2","createTime":"2021-10-10 11:52:02.249","updateTime":null} {"index":{"_index":"blog","_id":3}} {"blogId":3,"title":"Spring Data ElasticSearch学习教程3","content":"这是批量添加的文档3","author":"Captain America","category":"ElasticSearch","status":1,"serialNum":"3","createTime":"2021-10-10 11:52:03.249","updateTime":null} {"index":{"_index":"blog","_id":4}} {"blogId":4,"title":"Spring Data ElasticSearch学习教程4","content":"这是批量添加的文档4","author":"Captain America","category":"ElasticSearch","status":1,"serialNum":"4","createTime":"2021-10-10 11:52:04.249","updateTime":null} {"index":{"_index":"blog","_id":5}} {"blogId":5,"title":"Spring Data ElasticSearch学习教程5","content":"这是批量添加的文档5","author":"Spider Man","category":"ElasticSearch","status":1,"serialNum":"5","createTime":"2021-10-10 11:52:05.249","updateTime":null} {"index":{"_index":"blog","_id":6}} {"blogId":6,"title":"Java学习教程6","content":"这是批量添加的文档6","author":"Spider Man","category":"ElasticSearch","status":1,"serialNum":"6","createTime":"2021-10-10 11:52:06.249","updateTime":null} {"index":{"_index":"blog","_id":7}} {"blogId":7,"title":"Java学习教程7","content":"这是批量添加的文档7","author":"Iron Man","category":"ElasticSearch","status":1,"serialNum":"7","createTime":"2021-10-10 11:52:07.249","updateTime":null} {"index":{"_index":"blog","_id":8}} {"blogId":8,"title":"Java学习教程8","content":"这是批量添加的文档8","author":"Iron Man","category":"ElasticSearch","status":1,"serialNum":"8","createTime":"2021-10-10 11:52:08.249","updateTime":null} {"index":{"_index":"blog","_id":9}} {"blogId":9,"title":"Java学习教程9","content":"这是批量添加的文档9","author":"Captain America","category":"ElasticSearch","status":1,"serialNum":"9","createTime":"2021-10-10 11:52:09.249","updateTime":null} {"index":{"_index":"blog","_id":10}} {"blogId":10,"title":"Java学习教程10","content":"这是批量添加的文档10","author":"God Of Thunder","category":"ElasticSearch","status":1,"serialNum":"10","createTime":"2021-10-10 11:52:10.249","updateTime":null}
执行后的结果
实体类
package com.example.demo.entity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import java.util.Date; @Data @Document(indexName = "blog", shards = 1, replicas = 1) public class Blog { //此项作为id,不会写到_source里边。 @Id private Long blogId; @Field(type = FieldType.Text) private String title; @Field(type = FieldType.Text) private String content; @Field(type = FieldType.Text) private String author; //博客所属分类。 @Field(type = FieldType.Keyword) private String category; //0: 未发布(草稿) 1:已发布 2:已删除 @Field(type = FieldType.Integer) private int status; //序列号,用于给外部展示的id @Field(type = FieldType.Keyword) private String serialNum; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") @Field(type= FieldType.Date, format= DateFormat.custom, pattern="yyyy-MM-dd HH:mm:ss.SSS") private Date createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") @Field(type=FieldType.Date, format=DateFormat.custom, pattern="yyyy-MM-dd HH:mm:ss.SSS") private Date updateTime; }
索引操作(IndexOperations)
本处只是展示索引的操作方法,项目中不会这样创建索引,而是手写DSL去创建。
package com.example.demo.controller;
import com.example.demo.entity.Blog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 本处只是展示索引的操作方法,项目中不会这样创建索引,而是手写DSL去创建。
*/
@Api(tags = "索引操作(用不到)")
@RestController
@RequestMapping("index")
public class IndexController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@ApiOperation("创建索引")
@PostMapping("createIndex")
public String createIndex() {
// 创建索引,会根据Blog类的@Document注解信息来创建
elasticsearchRestTemplate.createIndex(Blog.class);
// 配置映射,会根据Item类中的id、Field等字段来自动完成映射
elasticsearchRestTemplate.putMapping(Blog.class);
return "success";
}
@ApiOperation("创建索引")
@PostMapping("deleteIndex")
public String deleteIndex() {
// 删除索引,会根据Blog类的@Document注解信息来删除
elasticsearchRestTemplate.deleteIndex(Blog.class);
return "success";
}
}
文档操作(DocumentOperations)
本处介绍常用的一些方法,这些方法能够满足开发中所有需求:
- 添加单个文档
- 添加多个文档
- 修改单个文档数据
- 修改单个文档部分数据
- 修改多个文档部分数据
- 查看单个文档
- 删除单个文档(根据id)
- 删除单个文档(根据条件)
- 删除所有文档。
package com.example.demo.controller; import com.example.demo.entity.Blog; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.elasticsearch.index.query.QueryBuilders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.Date; import java.util.List; @Api(tags = "增删改查(RestTemplate方式)") @RestController @RequestMapping("crudViaRestTemplate") public class CrudViaRestTemplateController { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @ApiOperation("添加单个文档") @PostMapping("addDocument") public Blog addDocument() { Long id = 1L; Blog blog = new Blog(); blog.setBlogId(id); blog.setTitle("Spring Data ElasticSearch学习教程" + id); blog.setContent("这是添加单个文档的实例" + id); blog.setAuthor("Tony"); blog.setCategory("ElasticSearch"); blog.setCreateTime(new Date()); blog.setStatus(1); blog.setSerialNum(id.toString()); return elasticsearchRestTemplate.save(blog); } @ApiOperation("添加多个文档") @PostMapping("addDocuments") public Object addDocuments(Integer count) { List<Blog> blogs = new ArrayList<>(); for (int i = 1; i <= count; i++) { Long id = (long)i; Blog blog = new Blog(); blog.setBlogId(id); blog.setTitle("Spring Data ElasticSearch学习教程" + id); blog.setContent("这是添加单个文档的实例" + id); blog.setAuthor("Tony"); blog.setCategory("ElasticSearch"); blog.setCreateTime(new Date()); blog.setStatus(1); blog.setSerialNum(id.toString()); blogs.add(blog); } return elasticsearchRestTemplate.save(blogs); } /** * 跟新增是同一个方法。若id已存在,则修改。 * 无法只修改某个字段,只能覆盖所有字段。若某个字段没有值,则会写入null。 * @return 成功写入的数据 */ @ApiOperation("修改单个文档数据") @PostMapping("editDocument") public Blog editDocument() { Long id = 1L; Blog blog = new Blog(); blog.setBlogId(id); blog.setTitle("Spring Data ElasticSearch学习教程" + id); blog.setContent("这是修改单个文档的实例" + id); return elasticsearchRestTemplate.save(blog); } @ApiOperation("修改单个文档部分数据") @PostMapping("editDocumentPart") public UpdateResponse editDocumentPart() { long id = 1L; Document document = Document.create(); document.put("title", "修改后的标题" + id); document.put("content", "修改后的内容" + id); UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id)) .withDocument(document) .build(); UpdateResponse response = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("blog")); return response; } @ApiOperation("修改多个文档部分数据") @PostMapping("editDocumentsPart") public void editDocumentsPart(int count) { List<UpdateQuery> updateQueryList = new ArrayList<>(); for (int i = 1; i <= count; i++) { long id = (long) i; Document document = Document.create(); document.put("title", "修改后的标题" + id); document.put("content", "修改后的内容" + id); UpdateQuery updateQuery = UpdateQuery.builder(Long.toString(id)) .withDocument(document) .build(); updateQueryList.add(updateQuery); } elasticsearchRestTemplate.bulkUpdate(updateQueryList, IndexCoordinates.of("blog")); } @ApiOperation("查看单个文档") @GetMapping("findById") public Blog findById(Long id) { return elasticsearchRestTemplate.get( id.toString(), Blog.class, IndexCoordinates.of("blog")); } @ApiOperation("删除单个文档(根据id)") @PostMapping("deleteDocumentById") public String deleteDocumentById(Long id) { return elasticsearchRestTemplate.delete(id.toString(), Blog.class); } @ApiOperation("删除单个文档(根据条件)") @PostMapping("deleteDocumentByQuery") public void deleteDocumentByQuery(String title) { NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchQuery("title", title)) .build(); elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog")); } @ApiOperation("删除所有文档") @PostMapping("deleteDocumentAll") public void deleteDocumentAll() { NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchAllQuery()) .build(); elasticsearchRestTemplate.delete(nativeSearchQuery, Blog.class, IndexCoordinates.of("blog")); } }
本处就不贴出测试的截图了。功能都是测试通过的。
查询操作(SearchOperations)
简介
查询的方法
Query接口有一个抽象实现和三个实现:
本处我使用NativeSearchQuery。因为它更贴近ES,语法更偏向于ES原来的命令。CriteriaQuery的用法跟JPA的差不多。
构建Query
可通过new NativeSearchQueryBuilder()来构建NativeSearchQuery对象NativeSearchQuery中有众多的方法来为我们实现复杂的查询与筛选等操作。其中的build()返回NativeSearchQuery。
QueryBuilders构造复杂查询条件
NativeSearchQueryBuilder中接收QueryBuilder
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; return this; }
可以用QueryBuilders构造QueryBuilder对象
基本查询
通过标题和内容来查找博客。
package com.example.demo.controller; import com.example.demo.dao.BlogRepository; import com.example.demo.entity.Blog; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.metrics.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.ParsedMax; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Range; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHit; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.*; import java.util.stream.Collectors; @Api(tags = "动态查询") @RestController @RequestMapping("dynamicQuery") public class DynamicQueryController { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @ApiOperation("简单查询") @GetMapping("simple") public List<Blog> simple(String title, String content) { NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(title)) { boolQueryBuilder.must(QueryBuilders.matchQuery("title", title)); } if (StringUtils.isNotBlank(content)) { boolQueryBuilder.must(QueryBuilders.matchQuery("content", content)); } query.withQuery(boolQueryBuilder); SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class); List<Blog> blogs = new ArrayList<>(); for (SearchHit<Blog> searchHit : searchHits) { blogs.add(searchHit.getContent()); } return blogs; } }
测试
分页排序
通过标题和作者来查找博客,分页,并根据创建时间倒序排序。
@ApiOperation("分页排序") @GetMapping("pageAndSort") public Page<Blog> pageAndSort(String title, String author) { PageRequest pageRequest = PageRequest.of(0, 2); NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(title)) { boolQueryBuilder.must(QueryBuilders.matchQuery("title", title)); } if (StringUtils.isNotBlank(author)) { boolQueryBuilder.must(QueryBuilders.matchQuery("author", author)); } query.withQuery(boolQueryBuilder); query.withPageable(pageRequest); query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)); SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class); List<Blog> blogs = new ArrayList<>(); for (SearchHit<Blog> searchHit : searchHits) { blogs.add(searchHit.getContent()); } return new PageImpl<Blog>(blogs, pageRequest, searchHits.getTotalHits()); }
测试
去重
通过标题和作者来查找博客,根据作者去重。
@ApiOperation("去重") @GetMapping("collapse") public List<Blog> collapse(String title, String author) { PageRequest pageRequest = PageRequest.of(0, 2); NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(title)) { boolQueryBuilder.must(QueryBuilders.matchQuery("title", title)); } if (StringUtils.isNotBlank(author)) { boolQueryBuilder.must(QueryBuilders.matchQuery("author", author)); } query.withQuery(boolQueryBuilder); query.withPageable(pageRequest); query.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)); // 去重的字段不能是text类型。所以,author的mapping要有keyword,且通过author.keyword去重。 query.withCollapseField("author.keyword"); //query.withCollapseField("category"); SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class); List<Blog> blogs = new ArrayList<>(); for (SearchHit<Blog> searchHit : searchHits) { blogs.add(searchHit.getContent()); } return blogs; }
测试
聚合
查询标题中带有“java”的每个作者的文章数量。
@ApiOperation("聚合") @GetMapping("aggregation") public Map<String, Long> aggregation() { NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java")); query.withQuery(boolQueryBuilder); // 作为聚合的字段不能是text类型。所以,author的mapping要有keyword,且通过author.keyword聚合。 query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword")); // 不需要获取source结果集,在aggregation里可以获取结果 query.withSourceFilter(new FetchSourceFilterBuilder().build()); SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class); Aggregations aggregations = searchHits.getAggregations(); assert aggregations != null; //因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等 ParsedStringTerms per_count = aggregations.get("per_count"); Map<String, Long> map = new HashMap<>(); for (Terms.Bucket bucket : per_count.getBuckets()) { map.put(bucket.getKeyAsString(), bucket.getDocCount()); } return map; }
测试
嵌套聚合
查询标题中带有“java”的每个作者的文章数量,再查出各个作者的最新的发文时间。
@ApiOperation("嵌套聚合") @GetMapping("subAggregation") public Map<String, Map<String, Object>> subAggregation() { NativeSearchQueryBuilder query = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(QueryBuilders.matchQuery("title", "java")); query.withQuery(boolQueryBuilder); query.addAggregation(AggregationBuilders.terms("per_count").field("author.keyword") .subAggregation(AggregationBuilders.max("latest_create_time").field("createTime")) ); // 不需要获取source结果集,在aggregation里可以获取结果 query.withSourceFilter(new FetchSourceFilterBuilder().build()); SearchHits<Blog> searchHits = elasticsearchRestTemplate.search(query.build(), Blog.class); Aggregations aggregations = searchHits.getAggregations(); assert aggregations != null; //因为结果为字符串类型 所以用ParsedStringTerms。其他还有ParsedLongTerms、ParsedDoubleTerms等 ParsedStringTerms per_count = aggregations.get("per_count"); Map<String, Map<String, Object>> map = new HashMap<>(); for (Terms.Bucket bucket : per_count.getBuckets()) { Map<String, Object> objectMap = new HashMap<>(); objectMap.put("docCount", bucket.getDocCount()); ParsedMax parsedMax = bucket.getAggregations().get("latest_create_time"); objectMap.put("latestCreateTime", parsedMax.getValueAsString()); map.put(bucket.getKeyAsString(), objectMap); } return map; }
测试
请先
!