SpringBoot整合elasticsearch (java整合es)

1,211 阅读6分钟

欢迎大家进群,一起探讨学习

欢迎大家进群,一起讨论学习

微信公众号,每天给大家提供技术干货

在这里插入图片描述

博主技术平台地址


博主开源微服架构前后端分离技术博客项目源码地址,欢迎各位star


SpringBoot整合elasticsearch

pom引入

<!--elasticsearch-->
 	<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
 <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

yml配置

spring:
  data:
  elasticsearch:
    rest:
      uris: ["ip:9200"]
    elasticsearch:
      cluster-name: docker-cluster
      cluster-nodes: ip:9300 #配置es节点信息,逗号分隔,如果没有指定,则启动ClientNode
      properties:
        path:
          logs: ./elasticsearch/log #elasticsearch日志存储目录
          data: ./elasticsearch/data #elasticsearch数据存储目录

配置只需这么些,接下来就写一些demo来玩一下elaseticsearch

构建Item类

@Document(indexName = "item",type = "docs", shards = 1, replicas = 0)
public class Item {
    @Id
    private Long id;
    //文章使用分词器
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title; //标题
    @Field(type = FieldType.Keyword)
    private String category;// 分类
    @Field(type = FieldType.Keyword)
    private String brand; // 品牌
    @Field(type = FieldType.Double)
    private Double price; // 价格
    
}

创建ItemRepository并继承ElasticsearchRepository,有兴趣的可以看一下底层源码

public interface ItemRepository extends ElasticsearchRepository<Item,Long>{
    /**
     * @Description:根据价格区间查询  自定义查询
     * @Param price1
     * @Param price2
     */
    List<Item> findByPriceBetween(double price1, double price2);
 
    List<Item> findByTitle(String title1);
 
    List<Item> findByTitleIn(Collection<String> ss);
}

创建索引

@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class EsDemoApplicationTest{
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
 
 
    /**
     * @Description:创建索引,会根据Item类的@Document注解信息来创建
     */
    @Test
    public void testCreateIndex() {
       elasticsearchRestTemplate.indexOps(Item.class).create();
    }
 
    /**
     * @Description:删除索引
     */
    @Test
    public void testDeleteIndex() {
        elasticsearchRestTemplate.indexOps(Item.class).delete();
    }
	/**
     * @Description:判断索引是否存在
     */
    @Test
    public void testExistIndex() {
        elasticsearchRestTemplate.indexOps(Item.class).exists();
    }
    /**
     * @Description:删除
     */
    @Test
    public void delete() {
        elasticsearchRestTemplate.delete(1L, Item.class);
    }
    /**
     * 查询文档数据
     */
    @Test
    public void getDoc() {
        Item item = elasticsearchRestTemplate.get(String.valueOf(1L), Item.class);
        System.out.println(item);
    }

    /**
     * 修改文档数据
     */
    @Test
    public void updateDoc() {

        Map<String, Object> map = new HashMap<>();
        map.put("title", "abc");

        Document doc = Document.from(map);

        UpdateQuery updateQuery = UpdateQuery
                .builder(String.valueOf(1))
                .withDocument(doc)
                .build();
        IndexCoordinates indexCoordinates = IndexCoordinates.of("item");
        elasticsearchRestTemplate.update(updateQuery, indexCoordinates);
    }

    /**
     * 分页搜索数据
     * 使用QueryBuilder
     * termQuery("key", obj) 完全匹配
     * termsQuery("key", obj1, obj2..)   一次匹配多个值
     * matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性
     * multiMatchQuery("text", "field1", "field2"..);  匹配多个字段, field有通配符忒行
     * matchAllQuery();         匹配所有文件
     * idsQuery();         只查询一个id的
     * fuzzyQuery();          模糊查询 不能用通配符, 找到相似的
     */
    @Test
    public void search() {
        Pageable pageable = PageRequest.of(0, 10);

        SortBuilder<FieldSortBuilder> sortBuilder = new FieldSortBuilder("price")
                .order(SortOrder.DESC);

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.boolQuery().should(QueryBuilders.fuzzyQuery("title", "360")))
                .withPageable(pageable)
                .withSort(sortBuilder)
                .build();
        SearchHits<Item> search = elasticsearchRestTemplate.search(query, Item.class);
        System.out.println(search.getSearchHits());
    }

    /**
     * 高亮搜索
     */
    @Test
    public void highlight() {
        String preTag = "<font color='red'>";
        String postTag = "</font>";

        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.matchQuery("title", "360"))
                .withHighlightFields(new HighlightBuilder.Field("title")
                        .preTags(preTag)
                        .postTags(postTag))
                .build();
        SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);

        List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
        List<Map<String, Object>> hlList = new ArrayList<>();
        for (SearchHit h : searchHitList) {

            List<String> highlightField = h.getHighlightField("title");
            String nameValue = highlightField.get(0);
            String originalJson = JSON.toJSONString(h.getContent());
            JsonParser jj = new GsonJsonParser();
            Map<String, Object> myHighLight = jj.parseMap(originalJson);
            // 用高亮的搜索结果覆盖原字段值
            myHighLight.put("title", nameValue);
            System.out.println(myHighLight);

            hlList.add(myHighLight);
        }
        System.out.println(hlList);
    }
    /**
     * 高亮搜索 排序加分页
     */
    @Test
    public void highlight1() {
        String preTag = "<font color='red'>";
        String postTag = "</font>";
        Pageable pageable = PageRequest.of(0, 10);
        SortBuilder<FieldSortBuilder> sortBuilder = new FieldSortBuilder("price")
                .order(SortOrder.DESC);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(QueryBuilders.fuzzyQuery("title", "360"))
                .withPageable(pageable)
                .withSort(sortBuilder)
                .withHighlightFields(new HighlightBuilder.Field("title")
                        .preTags(preTag)
                        .postTags(postTag))
                .build();
        SearchHits<Item> searchHits = elasticsearchRestTemplate.search(query, Item.class);

        List<SearchHit<Item>> searchHitList = searchHits.getSearchHits();
        List<Map<String, Object>> hlList = new ArrayList<>();
        for (SearchHit h : searchHitList) {

            List<String> highlightField = h.getHighlightField("title");
            String nameValue = highlightField.get(0);
            String originalJson = JSON.toJSONString(h.getContent());
            JsonParser jj = new GsonJsonParser();
            Map<String, Object> myHighLight = jj.parseMap(originalJson);
            // 用高亮的搜索结果覆盖原字段值
            myHighLight.put("title", nameValue);
            hlList.add(myHighLight);
        }
        System.out.println(hlList);
    }
}

QueryBuilder构造ES查询条件使用规则

blog.csdn.net/csdn_201508…

第二种实现方式 RestHighLevelClient

package comn.hy.search.service.impl;
 
 
import com.hy.search.Stu;
import com.hy.utils.JsonUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.springframework.web.bind.annotation.*;
 
import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@RestController
public class TestServiceES {
 
    @Resource
    private RestHighLevelClient client;
 
 
    /**
     * 创建索引
     * @throws IOException
     */
    @PostMapping("create-index")
    public void createIndex() throws IOException {
        CreateIndexRequest request = new CreateIndexRequest("user_index");
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }
 
    /**
     * 获取索引信息
     * @throws IOException
     */
    @GetMapping("query-index")
    public void queryIndex() throws IOException {
        GetIndexRequest request = new GetIndexRequest("user_index");
 
        boolean b = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(b);
    }
 
 
    /**
     * 删除索引
     * @throws IOException
     */
   @DeleteMapping("delete-index")
    public void deleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("user_index");
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }
 
    /**
     * 新增一个文档
     * @throws IOException
     */
    @PostMapping("add-doc")
    public void addDoc() throws IOException {
 
        Stu stu = new Stu(10010L, "绝世风华", 18, 100.5f, true);
        IndexRequest request = new IndexRequest("user_index");
        // 规则 put /test_index/_doc/1
        request.id("1");
        request.timeout(new TimeValue(60, TimeUnit.SECONDS));
        request.source(JsonUtils.objectToJson(stu),XContentType.JSON);
        // 发送
        IndexResponse response = client.index(request,RequestOptions.DEFAULT);
        System.out.println(response.toString());
        System.out.println(response.status());
    }
 
    /**
     * 查询文档数据
     * @throws IOException
     */
    @GetMapping("query-doc")
    void queryDoc() throws IOException {
        GetRequest request = new GetRequest("user_index","1");
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_none_");
 
        // 是否存在???
        boolean b = client.exists(request,RequestOptions.DEFAULT);
        System.out.println(b);
 
        // 获取
        GetResponse response = client.get(request,RequestOptions.DEFAULT);
        System.out.println(response.getSourceAsString());
        System.out.println(response);
    }
 
    /**
     * 更新文檔
     * @throws IOException
     */
    @PutMapping("update-doc")
    public void updateDoc() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("user_index","1");
        updateRequest.timeout("1s");
        Stu stu = new Stu(10010L, "上善若水", 18, 100.5f, true);
        updateRequest.doc(JsonUtils.objectToJson(stu),XContentType.JSON);
 
        UpdateResponse response = client.update(updateRequest,RequestOptions.DEFAULT);
 
        System.out.println(response);
        System.out.println(response.status());
    }
 
    /**
     * 删除文档
     * @throws IOException
     */
    @DeleteMapping("delete-doc")
    public void deleteDoc() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("user_index","1");
        deleteRequest.timeout("1s");
        DeleteResponse response = client.delete(deleteRequest,RequestOptions.DEFAULT);
        System.out.println(response);
        System.out.println(response.status());
    }
 
 
 
    /**
     * 批量操作
     * @throws IOException
     */
    @PostMapping("bulk-operator")
    public void bulkOperator() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("15s");
 
        Stu stu0 = new Stu(10010L, "诸天万界之起源传说", 18, 100.5f, true);
        Stu stu1 = new Stu(10011L, "寒夜", 20, 88.5f, true);
        Stu stu2 = new Stu(10012L, "陌上千寻雪", 22, 96.5f, false);
        Stu stu3 = new Stu(10013L, "可爱的漂亮的小哥哥", 26, 108.5f, false);
        Stu stu4 = new Stu(10014L, "灵纪传说", 28, 108.6f, true);
        Stu stu5 = new Stu(10015L, "狂剑天下之鸿蒙掌控", 16, 18.5f, false);
        Stu stu6 = new Stu(10016L, "逆战次元", 29, 100.5f, true);
 
        ArrayList<Stu> stuList = new ArrayList<>();
        stuList.add(stu0);
        stuList.add(stu1);
        stuList.add(stu2);
        stuList.add(stu3);
        stuList.add(stu4);
        stuList.add(stu5);
        stuList.add(stu6);
 
        for(int i = 0;i<=stuList.size();i++){
            bulkRequest.add(
                    new IndexRequest("test.index").
                            id(""+(i+1)).
                            source(JsonUtils.objectToJson(stuList.get(i)), XContentType.JSON));
        }
        // 批量插入
        BulkResponse response = client.bulk(bulkRequest,RequestOptions.DEFAULT);
        System.out.println(response.hasFailures());
    }
 
 
    /**
     * 条件查询
     * @throws IOException
     */
    @GetMapping("condition-search")
    public void conditionSearch() throws IOException {
        SearchRequest searchRequest = new SearchRequest("user_index");
        // 构造条件
        SearchSourceBuilder builder = new   SearchSourceBuilder();
        // 精确匹配
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name","绝世风华");
 
        // 匹配全部 matchAllQuery();
        builder.query(termQueryBuilder);
 
        // 分页
        builder.from(0);
        builder.size(10);
        builder.timeout(TimeValue.MINUS_ONE);
        searchRequest.source(builder);
        SearchResponse response = client.search(searchRequest,RequestOptions.DEFAULT);
        System.out.println(JsonUtils.objectToJson(response.getHits()));
 
        for (org.elasticsearch.search.SearchHit hit : response.getHits().getHits()) {
            hit.getSourceAsMap().get("name");
        }
    }
 
 
}
 

先执行创建索引在这里插入图片描述 索引数据操作

@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootApplication.class)
public class ceshiTest {
    @Autowired
    private ItemRepository itemRepository;
 
    /**
     * @Description:定义新增方法
     */
    @Test
    public void insert() {
        Item item = new Item(1L, "小米手机7", " 手机",
                "小米", 3499.00);
        itemRepository.save(item);
    }
 
 
 
    /**
     * @Description:定义批量新增方法
     */
    @Test
    public void insertList() {
        List<Item> list = new ArrayList<>();
        list.add(new Item(1L, "小米9", "手机", "小米", 3299.00));
        list.add(new Item(2L, "华为pro30", "手机", "华为", 3999.00));
        list.add(new Item(3L, "一加7", "手机", "一加", 2999.00));
        list.add(new Item(4L, "魅族16", "手机", "魅族", 1999.00));
        list.add(new Item(5L, "苹果xs", "手机", "苹果", 5099.00));
        list.add(new Item(6L, "360pro", "手机", "360", 1099.00));
        list.add(new Item(7L, "荣耀V10", "手机", "华为", 899.00 ));
        // 接收对象集合,实现批量新增
        itemRepository.save(list);
    }
    

    
    /**
     * @Description:按照价格区间查询  自定义方法
     * 自定义方法
        Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
        比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
        当然,方法名称要符合一定的约定  下边为约定
            And	findByNameAndPrice
            Or	findByNameOrPrice
            Is	findByName
            Not	findByNameNot
            Between	findByPriceBetween
            LessThanEqual	findByPriceLessThan
            GreaterThanEqual	findByPriceGreaterThan
            Before	findByPriceBefore
            After	findByPriceAfter
            Like	findByNameLike
            StartingWith	findByNameStartingWith
            EndingWith	findByNameEndingWith
            Contains/Containing	findByNameContaining
            In	findByNameIn(Collection<String>names)
            NotIn	findByNameNotIn(Collection<String>names)
            Near	findByStoreNear
            True	findByAvailableTrue
            False	findByAvailableFalse
            OrderBy	findByAvailableTrueOrderByNameDesc
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void queryByPriceBetween(){
        List<Item> list = this.itemRepository.findByPriceBetween(2000.00, 3500.00);
        for (Item item : list) {
            System.out.println("item = " + item.getTitle());
        }
    }
 
 
    @Test
    public void queryByTitle(){
        List<Item> list = this.itemRepository.findByTitle("华为");
        for (Item item : list) {
            System.out.println("item = " + item.getTitle());
        }
    }
 
    @Test
    public void queryByTitleTo(){
        Collection<String> ss =  new ArrayList<>();
        ss.add("华为");
        ss.add("小米");
        List<Item> list = this.itemRepository.findByTitleIn(ss);
        for (Item item : list) {
            System.out.println("item = " + item.getTitle());
        }
    }
 
    /**
     * @Description:matchQuery底层采用的是词条匹配查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testMatchQuery(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", "华为"));
        // 搜索,获取结果
        Page<Item> items = this.itemRepository.search(queryBuilder.build());
        // 总条数
        long total = items.getTotalElements();
        System.out.println("获取的总条数 = " + total);
        for (Item item : items) {
            System.out.println("手机名称是:"+item.getTitle());
        }
    }
 
 
    /**
     * @Description:
     * termQuery:功能更强大,除了匹配字符串以外,还可以匹配
     * int/long/double/float/....
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testTermQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.termQuery("price",1099));
        // 查找
        Page<Item> page = this.itemRepository.search(builder.build());
 
        for(Item item:page){
            System.out.println("手机是:"+item.getTitle());
        }
    }
    /**
     * @Description:布尔查询  多条件查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testBooleanQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
 
        builder.withQuery(
                QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title","华为"))
                        .must(QueryBuilders.matchQuery("brand","华为"))
        );
 
        // 查找
        Page<Item> page = this.itemRepository.search(builder.build());
        for(Item item:page){
            System.out.println("手机名称是"+item.getTitle());
        }
    }
 
 
    /**
     * @Description:布尔查询  多条件查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testBlQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
 
        builder.withQuery(
                QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title","荣耀"))
                        .must(QueryBuilders.matchQuery("title","华为"))
        );
 
        // 查找
        Page<Item> page = this.itemRepository.search(builder.build());
        for(Item item:page){
            System.out.println("手机名称是"+item.getTitle());
        }
    }
    /**
     * @Description:模糊查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testFuzzyQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.fuzzyQuery("title","一"));
        Page<Item> page = this.itemRepository.search(builder.build());
        for(Item item:page){
            System.out.println("手机名称是:"+item.getTitle());
        }
 
    }
 
    /**
     * @Description:分页查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void searchByPage(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
        // 分页:
        int page = 0;
        int size = 2;
        queryBuilder.withPageable(PageRequest.of(page,size));
 
        // 搜索,获取结果
        Page<Item> items = this.itemRepository.search(queryBuilder.build());
        // 总条数
        long total = items.getTotalElements();
        System.out.println("总条数 = " + total);
        // 总页数
        System.out.println("总页数 = " + items.getTotalPages());
        // 当前页
        System.out.println("当前页:" + items.getNumber());
        // 每页大小
        System.out.println("每页大小:" + items.getSize());
 
        for (Item item : items) {
            System.out.println(item.getTitle());
        }
    }
 
    /**
     * @Description:排序查询
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void searchAndSort(){
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机"));
 
        // 排序
        queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
 
        // 搜索,获取结果
        Page<Item> items = this.itemRepository.search(queryBuilder.build());
        // 总条数
        long total = items.getTotalElements();
        System.out.println("总条数 = " + total);
 
        for (Item item : items) {
            System.out.println("手机的价格是:"+item.getTitle()+":"+item.getPrice());
        }
    }
 
    /**
     * @Description:按照品牌brand进行分组
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand"));
        // 2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
        // 3、解析
        // 3.1、从结果中取出名为brands的那个聚合,
        // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        // 3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            // 3.4、获取桶中的key,即品牌名称
            System.out.println(bucket.getKeyAsString());
            // 3.5、获取桶中的文档数量
            System.out.println(bucket.getDocCount());
        }
 
    }
 
 
    /**
     * @Description:嵌套聚合,求平均值
     * @Author: https://blog.csdn.net/chen_2890
     */
    @Test
    public void testSubAgg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand")
                        .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值
        );
        // 2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build());
        // 3、解析
        // 3.1、从结果中取出名为brands的那个聚合,
        // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        // 3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            // 3.4、获取桶中的key,即品牌名称  3.5、获取桶中的文档数量
            System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台");
 
            // 3.6.获取子聚合结果:
            InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg");
            System.out.println("平均售价:" + avg.getValue());
        }
 
    }
}