Elasticsearch

我们从表中做搜索功能可以通过 SQL 语句中的 like 语法实现查询,但是会存在几个问题:

  1. 针对单表的全表扫描效率低
  2. 关系数据库中提供的查询功能弱
  3. 关键字匹配比较随意

为解决 搜索功能,我们引入 Elasticsearch 技术


ES 介绍

Elasticsearch 是一个基于 Lucene 的 分布式高扩展高实时 的基于RESTful 风格 API 的 搜索与数据分析引擎

基本概念

  • 字段 (Field): 一个字段表示一个属性,类比于数据库表中的属性,数据库中的一行数据通常是多个属性值组成

  • 文档 (document):在ES中数据的存储和关系数据库不同,所有的数据都是以文档的JSON document的形式存在,document是ES中 索引搜索 的最小的数据单位,类比于数据库中的一行数据,通常有多个字段值组成

  • 映射 (mapping): mapping定义了document中每个字段的类型、字段所使用的分词器等。相当于关系型数据库中的表结构。

  • 索引(index): ElasticSearch存储数据的地方,可以理解成关系型数据库中的数据库概念,存放一类相同或者类似的document,比如一个员工索引,商品索引。

  • 类型 (Type):逻辑上的数据分类,一种type就像一张表。如用户表、角色表等。在Elasticsearch6.X默认 type为_doc,es 7.x中删除了type的概念

ES 可以实现高效的全文检索功能,其中一个很重要的原因是 ES 使用了 倒排索引

这里要注意的一点是,倒排索引和文档的存储本身没有关系,只是为了快速的全文检索,针对document的一个或者多个字段,所创建的索引

正/倒排索引

正排索引:文档id 对应 分词

倒排索引:分词 对应 文档id

为什么ES效率高

  1. 分布式架构:Elasticsearch本身设计为分布式系统,能够轻松扩展以支持大规模数据,并通过集群管理多个节点,实现高可用性和负载均衡。

  2. 基于Lucene:Elasticsearch建立在Apache Lucene之上,这个底层库以高效的全文搜索能力著称。Elasticsearch在Lucene的基础上进行了优化,使查询速度更快。

  3. 实时搜索:Elasticsearch支持近实时的搜索功能,通过底层的倒排索引结构,能够在数据写入后立即进行搜索,提供高效的查询性能。

  4. 强大的查询DSL:它提供了丰富的查询语法,可以进行复杂的布尔查询、范围查询、聚合查询、全文本检索等。

  5. 高效的索引机制:Elasticsearch通过分片(sharding)和副本(replication)机制管理索引,不仅提升了查询性能,还保证了数据的冗余和高可用性。

  6. 缓存机制:内建的缓存功能会缓存常用查询结果和索引数据,加速后续查询。

ES 操作

ES 虽然是基于 Java 语言开发的,但它的使用却不仅仅局限于 Java 语言,因为 ES 对外提供了 Restful 风格的 API,我们可以通过这些 Restful 风格的 API 向 ES 发送请求,从而操作 ES

操作索引

  • 创建索引 PUT请求
 PUT http://ip:端口/索引名称
  • 查询索引 GET请求
 GET http://ip:端口/索引名称    # 查询单个索引信息  
 GET http://ip:端口/索引名称1,索引名称2...  # 查询多个索引信息  
 GET http://ip:端口/_all       # 查询所有索引信息
  • 删除索引 DELETE请求
 DELETE http://ip:端口/索引名称
  • 关闭、打开索引 POST请求
 POST http://ip:端口/索引名称/_close    
 POST http://ip:端口/索引名称/_open

数据类型

  1. 简单数据类型
  • 字符串
 text:会分词,不支持聚合  
 keyword:不会分词,将全部内容作为一个词条,支持聚合
  • 数值:long, integer,short, byte,double,float,half_float,scaled_float

half_float:半精度浮点数
scaled_float:可缩放的浮点数(整数)

  • “type”: “scaled_float”
  • “scaling_factor”: 100 (每个字段都需要绑定一个缩放因子)
  • 布尔:boolean

  • 二进制:binary

  • 范围类型

 integer_range, float_range, long_range, double_range, date_range 
  • 日期:date

字符串中,text 和 keyword 是由数据类型决定的分词与不分词,是指在保存文档数据,添加倒排索引项的时候发生的

  1. 复杂数据类型
  • 数组:[ ] 没有专门的数组类型,ES 会自动处理数组类型数据

  • 对象:{ } Object:object (for single JSON objects 单个JSON对象)

基本操作

创建索引

PUT student

添加映射

#添加映射
PUT student/_mapping
{
	"properties":{
		 "name":{
		   "type":"text"
		 },
		 "age":{
		   "type":"integer"
		 }
	}
}

查询映射

# 查询映射
GET student/_mapping

# 创建并查询映射
PUT student
{
  "mappings": {
    "properties": {
      "id":{
        "type": "integer"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

删除索引

DELETE student

修改映射

# 添加索引
PUT student/_mapping
{
  "properties":{
    "age":{
      "type": "integer"
    }
  }
}

# 不能修改已有字段,需要删除重新添加

添加文档

 # 指定唯一标识
POST teacher/_doc/cskaoyan
{  
	"name":"张三",  
	"age":18,  
	"address":"北京"  
}# 不指定唯一标识,会自动生成
​POST student/_doc/
{
	"id":2,
	"name":"lisi",
	"age":19
}

查询文档

# 查询一条文档
GET student/_doc/cskaoyan

# 查询所有文档  
GET /teacher/_search

修改文档

# 全量修改,会完全覆盖原本的值
POST student/_doc/cskaoyan001
{
  "id":100,
  "name":"kongling"
}

# 增量修改
POST teacher/_update/cskaoyan  
{  
	"doc": {  
		 "name": "李四"  
	}  
}

删除文档

# 删除指定id文档  
DELETE student/_doc/cskaoyan

IK 分词器

对于中文内容的分词,我们通常会采用对中文支持比较好的 IK分词器

IKAnalyzer 是一个开源的,基于 java 语言开发的轻量级的中文分词工具包,基于 Maven 构建,具有 60万字/秒 的高速处理能力,并且支持用户词典扩展定义

IK分词器使用

IK分词器有两种分词模式:ik_max_wordik_smart 模式

ik_max_word

会将文本做 最细粒度 的拆分

  • 比如会将 “好好学习, 天天向上” 拆分为
    好好学习,好好学、好好、好学、学习、天天向上、天天,向上
#方式一ik_max_word
GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "好好学习, 天天向上"
}
  • ik_max_word 分词器执行如下:
{
  "tokens" : [
    {
      "token" : "好好学习",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "好好学",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "好好",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "好学",
      "start_offset" : 1,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "学习",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "天天向上",
      "start_offset" : 6,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "天天",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "向上",
      "start_offset" : 8,
      "end_offset" : 10,
      "type" : "CN_WORD",
      "position" : 7
    }
  ]
}

ik_smart

会做 最粗粒度 的拆分

  • 比如会将“好好学习, 天天向上”拆分为好好学习、天天向上。
#方式二 ik_smart
GET _analyze
{
  "analyzer": "ik_smart",
  "text": "好好学习, 天天向上"
}
  • ik_smart分词器 执行如下:
{
  "tokens" : [
    {
      "token" : "好好学习",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "天天向上",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 1
    }
  ]
}

指定分词器查询

文档的查询可以分为两种查询方式:

  • 词条查询term):词条查询 不会 分析查询条件,只有当词条和查询字符串 完全匹配时才匹配搜索
  • 全文查询match):全文查询 会 分析查询条件,先将查询条件进行分词,然后查询,求并集

准备工作如下

# 创建索引,添加映射,并指定分词器为ik分词器
PUT member
{
  "mappings": {
    "properties": {
      "name": {
        "type": "keyword"
      },
      "address": {
        "type": "text",
        "analyzer": "ik_max_word"
      }
    }
  }
}

# 添加文档
POST member/_doc/1
{
  "name":"zs",
  "age":18,
  "address":"武汉市洪山区"
}
POST member/_doc/2
{
  "name":"lisi",
  "age":18,
  "address":"武汉市"
}
POST /member/_doc/3
{
  "name":"ww",
  "age":18,
  "address":"武汉黄陂"
}

# 查询映射
GET member/_search

# 查看分词效果
GET _analyze
{
  "analyzer": "ik_max_word",
  "text": "武汉市洪山区"
}

下面分别采用两种方式查询,分词都是用IK分词器:

  • 词条查询:term

    查询member中匹配到"武汉"两字的词条

GET member/_search
{
  "query": {
    "term": {
      "address": {
        "value": "武汉"
      }
    }
  }
}
  • 全文查询:match

    全文查询会分析查询条件,先将查询条件进行分词,然后查询,求并集

GET member/_search
{
  "query": {
    "match": {
      "address":"武汉黄陂"
    }
  }
}

ES 批量操作

通过批量操作,我们可以一次向 Elastic Search 发送多条 增删改 操作。从而达到一定程度上节省带宽的操作

语法

# 定义索引,及其映射
PUT teacher
{
  "mappings": {
    "properties": {
    "id": {"type": "long"},
    "name": {"type": "text"},
    "age": {"type": "integer"}
   }
  }
}
#批量操作
#删除1号  新增2号  更新2号 
POST _bulk
{"create": {"_index": "teacher", "_id": "2"}}
{"name": "南风", "age": 18}

{"update": {"_index": "teacher", "_id": 2}}
{"doc": {"name":"景天", "age": 19}}

{"delete": {"_index":"teacher", "_id": "1"}}

批量从数据库导入数据

  • 创建索引和映射
PUT product
{
  "mappings": {
    "properties": {
      "id":{"type": "long"},
      "image": {"type":  "keyword"},
      "status": {"type": "integer"},
      "sellPoint": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "num":{"type": "integer"},
      "tmName": {"type": "keyword"},
      "cid": {"type": "long"},
      "price": {"type": "double"},
      "limitNum": {"type": "integer"},
      "created": {"type": "date"},
      "updated": {"type": "date"}
    }
  }
}
  • 代码实现从数据库批量将数据导入Elastic Search
    // 从Mysql 批量导入 elasticSearch
   @Test
   public void test3() throws IOException {
       //1.查询所有数据,mysql
       List<Item> products = productMapper.findAll();
       //2.bulk导入
       BulkRequest bulkRequest=new BulkRequest();
       //2.1 循环goodsList,创建IndexRequest添加数据
       for (Item item : items) {
           //将product对象转换为json字符串
           String data = JSON.toJSONString(item);
           IndexRequest indexRequest=new IndexRequest("product").source(data,XContentType.JSON);
           bulkRequest.add(indexRequest);
       }
       BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
       System.out.println(response.status());
   }

ES 高级查询

match all 全文查询

  • 查询所有文档
  • 可使用 "from" "size" 字段进行分页
# from:指定从查询结果集中的第几条文档开始返回
# size:返回文档的总条数
GET product/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 23
}
@Test
public void matchAll() throws IOException {
	// 构建查询请求对象,指定查询的索引名称
	SearchRequest searchRequest=new SearchRequest("product");
	// 创建查询条件构建器SearchSourceBuilder
	SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
	// 查询条件
	QueryBuilder queryBuilder= QueryBuilders.matchAllQuery();
	// 指定查询条件
	sourceBuilder.query(queryBuilder);
	// 添加查询条件构建器 SearchSourceBuilder
	searchRequest.source(sourceBuilder);
	//  添加分页信息  不设置 默认10条
	sourceBuilder.from(0);
	sourceBuilder.size(100);
	// 查询,获取查询结果
	SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
	// 获取命中对象 SearchHits
	SearchHits hits = searchResponse.getHits();
	// 获取总记录数
	Long total= hits.getTotalHits().value;
	System.out.println("总数:"+total);
	// 获取Hits数据  数组
	SearchHit[] result = hits.getHits();
	// 获取json字符串格式的数据
	List<Product> products = new ArrayList<>();
	for (SearchHit searchHit : result) {
		String sourceAsString = searchHit.getSourceAsString();
		//转为java对象
		Product product = JSON.parseObject(sourceAsString, Product.class);
		products.add(product);
	}
	 System.out.println(items);
}

term 不分词查询

term查询 和 字段类型有关系,首先回顾一下ElasticSearch两个数据类型

ElasticSearch 两个数据类型:

  • text:会分词,不支持聚合
  • keyword:不会分词,将全部内容作为一个词条,支持聚合

term查询:不会对查询条件进行分词

  • 但是注意,term查询,查询 text类型 字段时,文档中类型为 text类型 的字段本身仍然会分词
GET product/_search
{
  "query": {
    "term": {
      "title": {
        "value": "手机充电器"
      }
    }
  }
}
@Test
public void testTerm() throws IOException {
	// 构建查询请求对象,指定查询的索引名称
	SearchRequest searchRequest = new SearchRequest("product");
	// 创建查询条件构建器SearchSourceBuilder
	SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	// 查询条件
	TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "手机充电器");
	
	// 指定查询条件
	searchSourceBuilder.query(termQueryBuilder);
	//  添加查询条件构建器 SearchSourceBuilder
	searchRequest.source(searchSourceBuilder);
	
	// 查询,获取查询结果
	SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
	// 获取命中对象 SearchHits
	SearchHits hits = search.getHits();
	// 获取总记录数
	long value = hits.getTotalHits().value;
	ArrayList<Product> items = new ArrayList<>();
	SearchHit[] h = hits.getHits();
	for (int i = 0; i < value; i++) {
		Product product = JSON.parseObject(h[i].getSourceAsString(), Item.class);
		items.add(product);
	}
	System.out.println(items);
}

match 分词查询

match查询的特征:

  • 会对查询条件进行分词
  • 然后将分词后的查询条件和目标字段分词后的词条进行等值匹配
  • 默认取并集(OR),即只要查询条件中的一个分词和目标字段值的一个分词(词条)匹配,即认为匹配查询条件
# match查询
GET product/_search
{
  "query": {
    "match": {
      "title": "手机充电器"
    }
  },
  "size": 500
}
# AND 运算符:分词后同时存在才算匹配
GET product/_search
{
  "query": {
    "match": {
      "title": {
        "query": "华为手机",
        "operator": "and"
      }
    }
  },
  "size": 500
}
@Test
public void testMatch() throws IOException {
	// 构建查询请求对象,指定查询的索引名称
	SearchRequest searchRequest = new SearchRequest("product");
	// 创建查询条件构建器SearchSourceBuilder
	SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	// 查询条件
	MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "手机充电器");
	// 设置关键字查询的运算符
	matchQueryBuilder.operator(Operator.AND);
	// 指定查询条件
	searchSourceBuilder.query(matchQueryBuilder);
	
	// 添加查询条件构建器 SearchSourceBuilder
	searchRequest.source(searchSourceBuilder);
	
	// 查询,获取查询结果
	SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
	// 获取命中对象 SearchHits
	SearchHits hits = search.getHits();
	// 获取总记录数
	long value = hits.getTotalHits().value;
	ArrayList<Product> items = new ArrayList<>();
	SearchHit[] h = hits.getHits();
	for (int i = 0; i < value; i++) {
		Product product = JSON.parseObject(h[i].getSourceAsString(), Product.class);
		items.add(product);
	}
	System.out.println(items);
}

querystring 多字段查询

queryString 多条件查询

  1. 会对查询条件进行分词。
  2. 然后将分词后的查询条件和词条进行等值匹配
  3. 默认取并集(OR)
  4. 可以指定多个查询字段

query_string:可以识别 query 中的连接符(or 、and)

# queryString
GET product/_search
{
  "query": {
    "query_string": {
      "fields": ["title","sellPoint"], 
      "query": "耳机 AND 充电器"
    }
  }
}
@Test  
public void querystringQuery() throws IOException {  
    // 构造查询请求  
    SearchRequest searchRequest = new SearchRequest("product");  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  
    // 构造查询条件  
    QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("原装 AND 充电器").field("title").field("sellPoint");  
  
    // 设置具体的查询条件  
    searchSourceBuilder.query(queryStringQueryBuilder);  
    // 设置具体的请求参数  
    searchRequest.source(searchSourceBuilder);  
  
    // 发起查询请求  
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 解析响应,获取查询到的文档数据  
    List<Item> items = new ArrayList<>();  
    SearchHits searchHits = searchResponse.getHits();  
  
    long total = searchHits.getTotalHits().value;  
    System.out.println("total:" + total);  
  
    SearchHit[] hits = searchHits.getHits();  
    for (SearchHit hit : hits) {  
        // 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)  
        String docJson = hit.getSourceAsString();  
        // 将json转化为对象  
        Item item = JSON.parseObject(docJson, Item.class);  
        // 添加到结果集  
        items.add(item);  
    }  
    System.out.println(items);  
}

range & sort 范围排序查询

GET product/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 1000
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"   # 降序
}   }  ]  }
@Test  
public void rangeAndSort() throws IOException {  
    // 构造查询请求  
    SearchRequest searchRequest = new SearchRequest("product");  
  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  
  
    // 构造查询条件  
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(50).lte(1000);  
    // 设置排序  
    searchSourceBuilder.sort("price", SortOrder.DESC);  
  
    // 设置具体的查询条件  
    searchSourceBuilder.query(rangeQueryBuilder);  
    // 设置具体的请求参数  
    searchRequest.source(searchSourceBuilder);  
  
    // 发起查询请求  
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 解析响应,获取查询到的文档数据  
    List<Item> items = new ArrayList<>();  
    SearchHits searchHits = searchResponse.getHits();  
  
    long total = searchHits.getTotalHits().value;  
    System.out.println("total:" + total);  
  
    SearchHit[] hits = searchHits.getHits();  
    for (SearchHit hit : hits) {  
        // 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)  
        String docJson = hit.getSourceAsString();  
        // 将json转化为对象  
        Item item = JSON.parseObject(docJson, Item.class);  
        // 添加到结果集  
        items.add(item);  
    }  
    System.out.println(items);  
}

bool 复合查询

boolQuery:对多个查询条件连接。其组成主要分为如下四个部分:

  1. must(and):条件必须成立
  2. must_not(not):条件必须不成立
  3. should(or):条件可以成立
  4. filter:条件必须成立,性能比must高。不会计算得分
# boolquery 包含多个部分
GET product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "title": {
              "value": "充电器"
            }
          }
        }
      ],
      "filter":[ 
        {
        "term": {
          "title": "原装"
        }
       },
       {
         "range":{
          "price": {
            "gte": 40,
            "lte": 100
}   }   }   ]   }   }   }
@Test  
public void boolQuery() throws IOException {  
    // 构造查询请求  
    SearchRequest searchRequest = new SearchRequest("product");  
  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  
  
    // 构造查询条件  
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();  
    // 设置查询条件  
    MatchQueryBuilder matchQueryBuilder1 = QueryBuilders.matchQuery("title", "充电器");  
    boolQueryBuilder.must(matchQueryBuilder1);  
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(10).lte(100);  
    boolQueryBuilder.must(rangeQueryBuilder);  
    MatchQueryBuilder matchQueryBuilder2 = QueryBuilders.matchQuery("title", "原装");  
    boolQueryBuilder.should(matchQueryBuilder2);  
  
    // 设置具体的查询条件  
    searchSourceBuilder.query(boolQueryBuilder);  
    // 设置具体的请求参数  
    searchRequest.source(searchSourceBuilder);  
  
    // 发起查询请求  
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 解析响应,获取查询到的文档数据  
    List<Item> items = new ArrayList<>();  
    SearchHits searchHits = searchResponse.getHits();  
  
    long total = searchHits.getTotalHits().value;  
    System.out.println("total:" + total);  
  
    SearchHit[] hits = searchHits.getHits();  
    for (SearchHit hit : hits) {  
        // 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)  
        String docJson = hit.getSourceAsString();  
        // 将json转化为对象  
        Item item = JSON.parseObject(docJson, Item.class);  
        // 添加到结果集  
        items.add(item);  
    }  
    System.out.println(items);  
}

注意事项

  1. must 和 filter 区别是:must 会计算近似度得分
  2. 当 must 和 filter 同时存在时 会计算近似度得分,且整体性能更好,因为 filter 会过滤掉一些
  3. bool 查询 ,四个部分可以同时存在一个或多个
  4. should 和 must/filter 部分共存时可能会失效

aggs 聚合查询

聚合可以理解为增强,相当于给查询的结果再增加一个效果

聚合查询分为两种类型:

  • 指标聚合:相当于 MySQL 的聚合函数。max、min、avg、sum等
  • 桶聚合:相当于 MySQL 的 group by 操作
    • 不要对 text 类型的数据进行分组,会失败
# 聚合查询
# 指标聚合 聚合函数
GET product/_search
{
  "query": {
    "match": {
      "title": "耳机"
    }
  },
  "aggs": {
    "max_price": {
      "max": {
        "field": "price"
}   }   }   }
# 桶聚合  分组
GET product/_search
{
  "query": {
    "match": {
      "title": "充电器"
    }
  },
  "aggs": {
    "price_bucket": {
      "terms": {
        "field": "price",
        "size": 100
}   }   }   }
@Test
public void testAggQuery() throws IOException {
    SearchRequest searchRequest=new SearchRequest("product");
    SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
    MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "充电器");
    sourceBuilder.query(queryBuilder);
    // 查询价格列表  只展示前100条
    AggregationBuilder aggregation=AggregationBuilders.terms("price_bucket").field("price").size(100);
    sourceBuilder.aggregation(aggregation);
    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 获取命中对象 SearchHits
    SearchHits hits = searchResponse.getHits();
    // 获取总记录数
    Long total= hits.getTotalHits().value;
    System.out.println("总数:"+total);
    // aggregations 对象
    Aggregations aggregations = searchResponse.getAggregations();
    // 将aggregations 转化为map
    Map<String, Aggregation> aggregationMap = aggregations.asMap();
    // 通过key获取price_bucket 对象 使用Aggregation的子类接收  buckets属性在Terms接口中体现
    // Aggregation price_bucket = aggregationMap.get("price_bucket");
    Terms price_bucket =(Terms) aggregationMap.get("price_bucket");
    // 获取buckets 数组集合
    List<? extends Terms.Bucket> buckets = goods_brands.getBuckets();
    Map<String,Object>map=new HashMap<>();
    // 遍历buckets   key 属性名,doc_count 统计聚合数
    for (Terms.Bucket bucket : buckets) {
        System.out.println(bucket.getKey());
        map.put(bucket.getKeyAsString(),bucket.getDocCount());
    }
    System.out.println(map);
}
  • 嵌套聚合
@Test  
public void innerBucket() throws IOException {  
    // 构造查询请求  
    SearchRequest searchRequest = new SearchRequest("product");  
  
    // 构建搜索查询的对象  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  
    // 构造查询条件  
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(100).lte(1000);  
    // 设置具体的查询条件  
    searchSourceBuilder.query(rangeQueryBuilder);  
  
    // 构造聚合参数  
    // 1. 外层聚合  
    TermsAggregationBuilder outerAggregationsBuilder = AggregationBuilders.terms("price_bucket").field("price");  
    // 2. 内层聚合  
    TermsAggregationBuilder innerAggregationsBuilder = AggregationBuilders.terms("tm_bucket").field("tmName");  
    // 3. 给外层聚合设置内层聚合  
    outerAggregationsBuilder.subAggregation(innerAggregationsBuilder);  
    // 设置聚合参数  
    searchSourceBuilder.aggregation(outerAggregationsBuilder);  
  
    // 设置具体的请求参数  
    searchRequest.source(searchSourceBuilder);  
    // 发起查询请求  
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 获取aggregations  
    Aggregations aggregations = searchResponse.getAggregations();  
    // 根据聚合名称获取聚合结果  
    Terms priceBucket = aggregations.get("price_bucket");  
    // 获取所有分桶  
    List<? extends Terms.Bucket> outerBuckets = priceBucket.getBuckets();  
    // 最终结果集  
    List<PriceAggregationInfo> result = new ArrayList<>();  
    // 遍历外层分桶  
    for (Terms.Bucket outerBucket : outerBuckets) {  
        // 定义品牌集合  
        PriceAggregationInfo priceAggregationInfo = new PriceAggregationInfo();  
  
        double key = outerBucket.getKeyAsNumber().doubleValue();  
        priceAggregationInfo.setPrice(key);  
        long outerDocCount = outerBucket.getDocCount();  
        System.out.println("outer: key:" + key + ",value:" + outerDocCount);  
  
        // 从外层分桶中获取内层分桶聚合的结果  
        Aggregations innerAggregations = outerBucket.getAggregations();  
        Terms innerTerms = innerAggregations.get("tm_bucket");  
  
        List<String> tmList = new ArrayList<>();  
        List<? extends Terms.Bucket> innerBuckets = innerTerms.getBuckets();  
        for (Terms.Bucket innerBucket : innerBuckets) {  
            // 获取内层分桶的key,即品牌  
            String tmKey = innerBucket.getKeyAsString();  
            long innerDocCount = innerBucket.getDocCount();  
            // 添加到品牌集合中  
            tmList.add(tmKey);  
        }  
        priceAggregationInfo.setTrademarkNames(tmList);  
        result.add(priceAggregationInfo);  
    }  
    System.out.println(result);  
}

highlight 高亮查询

高亮三要素:

  1. 高亮字段
  2. 前缀
  3. 后缀

默认前后缀 :em

GET product/_search
{
  "query": {
    "match": {
      "title": "充电器"
    }
  },
  "highlight": {
    "fields": {
      "title": {
        "pre_tags": "<font color='red'>",
        "post_tags": "</font>"
}   }   }   }
@Test  
public void highlightQuery() throws IOException {  
  
    // 构造查询请求  
    SearchRequest searchRequest = new SearchRequest("product");  
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();  

    // 构造查询条件  
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "充电器");  
    // 设置具体的查询条件  
    searchSourceBuilder.query(termQueryBuilder);  
    // 设置高亮参数  
    HighlightBuilder highlightBuilder = new HighlightBuilder();  
    highlightBuilder.field("title").preTags("<font color='red'>").postTags("</font>");  
    searchSourceBuilder.highlighter(highlightBuilder);  

    // 设置具体的请求参数  
    searchRequest.source(searchSourceBuilder);  
    // 发起查询请求  
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);  
  
    // 解析响应,获取查询到的文档数据  
    List<Item> items = new ArrayList<>();  
    SearchHits searchHits = searchResponse.getHits();  
  
    long total = searchHits.getTotalHits().value;  
    System.out.println("total:" + total);  
  
    SearchHit[] hits = searchHits.getHits();  
    for (SearchHit hit : hits) {  
        // 获取 hit对象的 soutce属性值 (原始文档对应的json字符串)  
        String docJson = hit.getSourceAsString();  
        // 将json转化为对象  
        Item item = JSON.parseObject(docJson, Item.class);  
  
        // 获取高亮字符串  
        // key:字段名称  value:高亮的字符串  
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();  
        HighlightField highlightField = highlightFields.get("title");  
        // 获取高亮的字符串  
        String highlightString = highlightField.getFragments()[0].toString();  
        // System.out.println("highlightString:" + highlightString);  
  
        // 用高亮字符串替换原始值  
        item.setTitle(highlightString);  
  
        // 添加到结果集  
        items.add(item);  
    }  
    System.out.println(items);  
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值