【SpringCloud学习笔记】Elasticsearch

1. Elasticsearch

1.1 安装ES

  1. 启动Docker:service docker restart / systemctl restart docker
  2. 基于Docker创建网络docker network create hm-net
  3. 向云服务器上传elasticsearch以及kibana的tar包,并使用docker load -i xxx.tar进行加载
  4. 使用如下命令启动es:
docker run -d \
  --name es \
  -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  -e "discovery.type=single-node" \
  -v es-data:/usr/share/elasticsearch/data \
  -v es-plugins:/usr/share/elasticsearch/plugins \
  --privileged \
  --network hm-net \
  -p 9200:9200 \
  -p 9300:9300 \
  elasticsearch:7.12.1
  1. 使用如下命令启动kibana:
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=hm-net \
-p 5601:5601  \
kibana:7.12.1
  1. 使用docker logs -f es / kibana可查看运行日志:

image.png

  1. 验证在浏览器中访问:9200以及5601端口能观察到如下现象证明启动成功:

image.pngimage.png

1.2 初识Elasticsearch

1.2.1 简介

官方网站:https://www.elastic.co/cn/elasticsearch

**Elasticsearch:**是一个高性能的搜索引擎,它是ELK技术栈的一部分:

  • Logstash / Beats:用于数据收集
  • Elasticsearch:用于数据的存储、搜索、计算
  • Kibana:用于数据可视化(内置Devtools等高效工具)

**应用场景:**搜索引擎技术在日常生活中非常常见,例如:

  1. github等网站的高亮搜索关键词
  2. 百度/搜狗等搜索引擎
  3. 各大网站支持的模糊查询(关键字匹配)
  4. 打车软件地理位置搜索

1.2.2 倒排索引

ES的高性能得益于其底层实现的倒排索引
倒排索引中有两个重要概念:

  • 文档(Document):一条数据就是一个文档,例如商品记录、用户记录都是一个文档
  • 词条(Item):对于一个文档中的内容或者用户搜索句使用某种算法,基于语义划分得到的一组词就是词条,例如我喜欢学习Java,就可以分成:“我”、“喜欢”、“学习”、"Java"这些词条

倒排索引构建流程:
假设此时正向索引为:

idtitleprice
1小米手机4999
2华为手机3999
3华为手表2999
4小米汽车199999

此时每一条数据就是一个文档,我们可以对文档中的title内容构建倒排索引:

  1. 使用某种分词算法将title进行分词,例如"小米手机"就可以得到"小米"、"手机"两个词条
  2. 以词条作为键,文档id列表作为值
  3. 如果词条已经出现过,那么就在文档列表末尾追加当前文档,如果该词条没有出现过,则构建一个新的键值对,键为当前词条,值为当前文档id

当对上述四条数据分词结束后倒排索引为:

词条文档id列表
小米[1, 4]
手机[1, 2]
华为[2, 3]
手表[3]
汽车[4]

倒排索引工作流程:

  1. 此时当用户搜索关键词为"小米手表",使用相同的分词算法得出词条"小米"、“手表”
  2. 然后就会查询倒排索引,小米对应的文档id有1、4, 手表对应的文档有3
  3. 然后再根据正排索引查询id对应的具体文档内容

1.2.3 IK分词器

1.2.3.1 基本使用

我们已经了解到ES工作原理需要借助一定的分词算法进行分词匹配,那么就需要一个字典(dict)来保存一些常用的词语,分词算法才可以判断某一个序列是否可以作为一个词条。
而ES标准的分词器对于中文分词支持力度不大!例如我们尝试在devtools发起如下分词请求:

POST /_analyze
{
  "analyzer": "standard",
  "text": "我喜欢学习Java"
}

image.png
可以观察到"standard"模式下的响应结果并非是我们想得到的!这个时候我们就需要使用到IK分词器了
安装步骤:

  1. 方式一:使用在线安装的方式,输入以下命令:
# 在线安装
docker exec -it es ./bin/elasticsearch-plugin  install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
# 重启docker es服务
docker restart es
  1. 方式二:离线安装,将下载好的ik分词压缩包挂载在数据卷中
# 查看所有的数据卷位置
docker volume ls 
# 查看docker es-plugins数据卷位置
docker volume inspect es-plugins
# cd xxx
# 上传ik压缩包
# 重启docker es服务
docker restart es

此时再次发起请求,结果如下:
image.png

1.2.3.2 设置扩展词以及停用词

**扩展词:**我们需要自定义一些单词放在字典当中,例如"白嫖"、“米饭论坛”
**停用词:**在中文中一些语气词比如啊、哦、了是不需要进行分词的,可以剔除
前面我们提到IK分词器需要依赖字典,该配置文件就可以在ik文件夹目录中的/config/IKAnalyzer.cfg.xml进行配置扩展词以及暂停词的文件image.png
我们进行上述配置:然后重启es: docker restart es此后es就会读取同级目录下的ext.dic获取扩展字典当中的内容,读取stopwords.dic获取停用词列表
image.png
证明拓展词以及停用词配置成功!

1.2.4 索引库操作

  1. 创建索引库和映射:

请求方式:PUT
请求路径:/索引库名(自己定义)
请求参数:mapping映射

PUT /索引库名
{
  "mappings": {
    "properties": {
      "字段名": {
        "type": "text",
        "index": true,
        "analyzer": "ik_smart"
      },
      "字段名": {
        "type": object,
        "properties": {
          "字段名": {
            "type": boolean
          }
        }
      }
    }
  }
}
  1. 查询索引库:

请求方式:GET
请求路径:/索引库名
请求参数:无

GET /索引库名
  1. 删除索引库:

请求方式:DELETE
请求路径:/索引库名
请求参数:无

DELETE /索引库名
  1. 修改索引库

修改索引结构在es中是不被允许的,因此如果修改了字段结构会导致倒排索引重建,开销巨大,但是我们可以新增字段
请求方式:PUT
请求路径:/索引库名/_mapping

PUT /索引库名/_mapping
{
  "properties": {
    "新字段名": {}
  }
}

索引库操作总结:

  1. 新增:PUT /索引库名
  2. 查询:GET /索引库名
  3. 修改 PUT /索引库名/_mapping
  4. 删除:DELETE /索引库名

1.2.5 文档操作

1.3 Java客户端

1.3.1 Java客户端初始化

上述我们实践了借助Kibana发送请求,但是我们还是需要学习使用Java客户端编程的方式实现:

  1. 在pom文件中引入RestHignLevelClient依赖
<!-- 引入es依赖 -->
<dependency>
  <groupId>org.elasticsearch.client</groupId>
  <artifactId>elasticsearch-rest-high-level-client</artifactId>
  <version>7.12.1</version>
</dependency>
  1. 初始化RestHignLevelClient对象
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class EsDemoApplicationTests {

	private RestHighLevelClient client;

	@BeforeEach
	public void init() {
		client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://129.211.6.176:9200")));
	}

	@Test
	public void testClient() {
		System.out.println(client);
	}
}

1.3.2 创建映射以及索引库

原先我们在Kibana中使用如下HTTP请求配置映射信息以及索引库:

PUT /user
{
  "mappings": {
    "properties": {
      "desc": {
        "type": "text",
        "index": "true",
        "analyzer": "ik_smart"
      },
      "name": {
        "type": "text",
        "index": "false"
      },
      "age": {
        "type": "integer",
        "index": "false"
      }
    }
  }
}

而在Java代码中我们需要通过以下的方式来创建:

/**
 * 测试创建索引库
 */
@Test
public void testCreateIndex() throws IOException {
    // 1. 创建Request对象
    CreateIndexRequest request = new CreateIndexRequest("user");
    // 2. 配置携带参数
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3. 发起请求
    client.indices().create(request, RequestOptions.DEFAULT);
}

1.3.3 查看索引库

原先我们在Kibana中使用如下HTTP请求查看索引库:

GET /user
/**
 * 测试获取索引库信息
 */
@Test
public void testGetIndex() throws IOException {
    // 1. 创建request对象
    GetIndexRequest request = new GetIndexRequest("user");
    // 2. 发起请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println("exists: " + exists);
}

1.3.4 删除索引库

原先我们在Kibana中使用如下HTTP请求删除索引库:

DELETE /user
/**
 * 测试删除索引库
 */
@Test
public void testDeleteIndex() throws IOException {
    // 1. 创建request对象
    DeleteIndexRequest request = new DeleteIndexRequest("user");
    // 2. 发起请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

1.3.5 新增文档

原先我们在Kibana中使用如下HTTP请求增加文档:

# 插入文档
POST /user/_doc/1
{
  "desc": "测试描述",
  "name": "ricejson",
  "age": 20
}
/**
 * 新增文档
 */
@Test
public void testCreateDocument() throws IOException {
    // 1. 创建request
    IndexRequest request = new IndexRequest("user").id("1");
    // 创建用户对象
    User user1 = new User("测试描述", "ricejson", 20);
    Gson gson = new Gson();
    String userStr = gson.toJson(user1);
    // 2. 填写内容
    request.source(userStr, XContentType.JSON);
    // 3. 发起请求
    client.index(request, RequestOptions.DEFAULT);
}

1.3.6 查看文档

原先我们在Kibana中使用如下HTTP请求查看文档:

GET /user/_doc/1
/**
 * 获取文档
 */
@Test
public void testGetDocument() throws IOException {
    // 1. 创建request
    GetRequest request = new GetRequest("user", "1");
    // 2. 发起请求
    GetResponse resp = client.get(request, RequestOptions.DEFAULT);
    // 3. 获取返回内容
    String source = resp.getSourceAsString();
    System.out.println(source);
}

1.3.7 删除文档

原先我们在Kibana中使用如下HTTP请求删除文档:

DELETE /user/_doc/1
/**
 * 删除文档
 */
@Test
public void deleteDocument() throws IOException {
    // 1. 创建request对象
    DeleteRequest request = new DeleteRequest("user", "1");
    // 2. 发起删除文档请求
    client.delete(request, RequestOptions.DEFAULT);
}

1.3.8 修改文档

全量修改:与新增文档一致
局部修改:

POST /user/_update/1
{
  "doc": {
    "name": "米饭好好吃"
  }
}
/**
 * 局部更新文档
 */
@Test
public void updateDocument() throws IOException {
    // 1. 创建request对象
    UpdateRequest request = new UpdateRequest("user", "1");
    // 2. 设置更新内容
    request.doc("name", "米饭好好吃");
    // 3. 发起请求
    client.update(request, RequestOptions.DEFAULT);
}

1.3.9 批处理

/**
 * 测试批处理操作
 */
@Test
public void testBatch() throws IOException {
    // 1. 创建request对象
    BulkRequest bulkRequest = new BulkRequest();
    // 2. 添加多个request操作
    // 创建用户对象
    User user = new User("测试描述", "ricejson", 20);
    Gson gson = new Gson();
    String userStr = gson.toJson(user);
    bulkRequest.add(new IndexRequest("user").id("1").source(userStr, XContentType.JSON));
    bulkRequest.add(new IndexRequest("user").id("2").source(userStr, XContentType.JSON));
    // 3. 发起批处理请求
    client.bulk(bulkRequest, RequestOptions.DEFAULT);
}

1.4 DSL语句

1.4.1 快速入门

现在我们需要设计一个商品表,其中MySQL存储如下字段内容:
id: int 编号
name: varchar(20) 名称
brand:varchar(20) 品牌
price: int 价格
desc: varchar(100) 描述内容

# 定义以及新增商品索引库
PUT /goods
{
  "mappings": {
    "properties": {
      "id": {
        "type": "integer"
      },
      "name": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "brand": {
        "type": "keyword"
      },
      "price": {
        "type": "integer"
      },
      "desc": {
        "type": "text",
        "analyzer": "ik_smart"
      }
    }
  }
}

现在插入几条模拟数据:

POST /goods/_doc/1
{
  "id": 1,
  "name": "超级智能手表",
  "brand": "FutureTech",
  "price": 1299,
  "desc": "这款超级智能手表拥有最新的健康监测功能,能够实时追踪您的心率和睡眠质量。"
}

POST /goods/_doc/2
{
  "id": 2,
  "name": "无线蓝牙耳机",
  "brand": "AudioMaster",
  "price": 499,
  "desc": "享受无线自由,这款无线蓝牙耳机提供卓越的音质和长达24小时的续航能力。"
}

POST /goods/_doc/3
{
  "id": 3,
  "name": "便携式咖啡机",
  "brand": "CoffeeCraft",
  "price": 699,
  "desc": "随时随地享受新鲜咖啡,这款便携式咖啡机设计精巧,操作简便,是咖啡爱好者的理想选择。"
}

POST /goods/_doc/4
{
  "id": 4,
  "name": "智能扫地机器人",
  "brand": "CleanRobotics",
  "price": 2999,
  "desc": "智能扫地机器人,自动导航,高效清洁,为您节省宝贵的时间。"
}

POST /goods/_doc/5
{
  "id": 5,
  "name": "多功能运动相机",
  "brand": "ActionCam",
  "price": 1499,
  "desc": "这款多功能运动相机防水防尘,适合各种极限运动场景,记录您的每一个精彩瞬间。"
}

搜索索引库中全部的文档内容语法如下:

GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

1.4.2 叶子查询

常见的叶子查询主要有如下两类:

  • 全量查询:要求查询条件必须是可以分词的
  • 精确查询
1.4.2.1 全量查询

其中全量查询语法格式如下:

# 全量查询
GET /goods/_search
{
  "query": {
    "match": {
      "name": "智能"
    }
  }
}

除此以外我们还可以指定查询多个字段:

# 全量查询(查询多个字段)
GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "您",
      "fields": ["name", "desc"]
    }
  }
}
1.4.2.2 精确查询

精确查询有如下两种常见方式:

  1. term:词条完全匹配
  2. range:范围查询,比如price >= 1000 以及 price <= 1999
# 精确查询(词条查询)
GET /goods/_search
{
  "query": {
    "term": {
      "brand": "ActionCam"
    }
  }
}
# 精确查询(范围查询)
GET /goods/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 1499,
        "lte": 2999
      }
    }
  }
}

1.4.3 复合查询

bool查询:
将多个叶子查询经过mustshouldmust_notfilter等与或非操作合并成一个复杂查询:

# 复合查询(查询name中包含机器人并且价格在1499-2999之间)
GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "机器人"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "price": {
              "gte": 1499,
              "lte": 2999
            }
          }
        }
      ]
    }
  } 
}

1.4.4 排序+分页

排序:
我们还可以在query的同级目录下加入order: {"排序字段": "asc|desc"}

  • ASC:表示正序
  • DESC:表示倒序
# 排序
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "sort": {
    "price": "asc"
  }
}

分页:
我们还可以在query的同级目录下加入"from": x, "size": x

  • from:表示从哪一条数据开始
  • size:表示单页的数量
# 分页
GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 3
}

1.4.5 高亮显示

想必大家都遇到过以下场景:
image.png
即百度搜索返回的结果中,与关键字匹配的结果都会高亮(红色)显示,打开开发者工具能够发现被高亮的部分由标签进行包括并使用CSS语法标红醒目。
那么问题就出现了,这个标签到底是由前端分析生成的还是由后端返回的?

  • 前端:如果让前端进行处理,都需要对查询内容进行分词,然后在内容中进行匹配,但是这样一来性能开销就非常大,前端一般只做数据展示,数据处理一般让后端处理
  • 后端:事实上ES在进行倒排索引的构建过程中,会保存查询分词在文档中的位置,然后在查询词前后加上等标签返回给前端。✔

DSL添加高亮显示:

# 高亮显示
GET /goods/_search
{
  "query": {
    "match": {
      "name": "智能"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

1.5 Java客户端操作DSL

1.5.1 快速入门

我们现在想要查询出索引库为goods下全部的文档内容,与之匹配的DSL语句如下:

GET /goods/_search
{
  "query": {
    "match_all": {}
  }
}

相对应的java代码如下:

/**
 * 测试查询全部文档
 */
@Test
public void testMatchAll() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 添加query查询条件
    request.source()
            .query(QueryBuilders.matchAllQuery());
    // 3. 发起请求获取响应
    SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析响应
    // 4.1 获取到hits
    SearchHits searchHits = resp.getHits();
    // 4.2 获取到数据条数
    TotalHits hitsCount = searchHits.getTotalHits();
    // 4.3 获取到数据数组
    SearchHit[] hitsHits = searchHits.getHits();
    // 4.3 逐个解析
    for (SearchHit searchHit : hitsHits) {
        // 4.4 获取source数据
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }
}

1.5.2 叶子查询

前面我们已经介绍过叶子查询主要有如下几类:

  1. 全量查询
    1. match:单字段匹配
    2. match_all:多字段匹配
  2. 精确查询
    1. term:根据词条内容匹配
    2. range:根据范围匹配

需求1:现在我们需要找出desc中包含"这款"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "match": {
      "desc": "这款"
    }
  }
}

对应的Java代码如下:

    /**
     * 查询desc中包含"这款"的内容
     */
    @Test
    public void testMatch() throws IOException {
        // 1. 创建request对象
        SearchRequest request = new SearchRequest("goods");
        // 2. 设置查询条件
        request.source()
                .query(QueryBuilders.matchQuery("desc", "这款"));
        // 3. 发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.1 获取到hits
        SearchHits hits = response.getHits();
        // 4.2 获取到数据总条数
        TotalHits totalHits = hits.getTotalHits();
        // 4.3 获取到数据集合
        SearchHit[] searchHits = hits.getHits();
        // 4.4 逐条解析
        for (SearchHit searchHit : searchHits) {
            String source = searchHit.getSourceAsString();
            System.out.println(source);
        }
    }

需求2:现在我们需要查询desc中包含"相机"并且name中包含"相机"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "multi_match": {
      "query": "相机",
      "fields": ["name", "desc"]
    }
  }
}

对应的Java代码如下:

/**
 * 查询desc并且name中都包含"相机"的文档
 */
@Test
public void testMultiMatch() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 添加查询条件
    request.source()
            .query(QueryBuilders.multiMatchQuery("相机", "name", "desc"));
    // 3. 发起请求获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取到hits
    SearchHits hits = response.getHits();
    // 4.2 获取到数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取全部查询文档
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析每条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source数据
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }

需求3:现在我们需要精确查找手机品牌为"ActionCam"的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "term": {
      "brand": "ActionCam"
    }
  }
}

对应的Java代码如下:

/**
 * 查询手机品牌为"ActionCam"的文档
 */
@Test
public void testTerm() throws IOException {
    // 1. 创建request对象
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.termQuery("brand", "ActionCam"));
    // 3. 发起请求获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取hits
    SearchHits hits = response.getHits();
    // 4.2 获取数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取全部匹配文档
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析出每一条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }

需求4:现在我们需要查询价格高于1999的文档
对应的DSL语句如下:

GET /goods/_search
{
  "query": {
    "range": {
      "price": {
        "gt": 1999
      }
    }
  }
}

对应的Java代码如下:

/**
 * 查询价格高于1499的文档
 */
@Test
public void testRange() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.rangeQuery("price").gt(1499));
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4.1 获取hits
    SearchHits hits = response.getHits();
    // 4.2 获取数据总数
    TotalHits totalHits = hits.getTotalHits();
    // 4.3 获取数据全集
    SearchHit[] searchHits = hits.getHits();
    // 4.4 解析每条数据
    for (SearchHit searchHit : searchHits) {
        // 4.5 获取source
        String source = searchHit.getSourceAsString();
        System.out.println(source);
    }
}

1.5.3 复合查询

需求:查询价格低于2999并且name中包含"耳机"并且品牌为"AudioMaster"的文档

GET /goods/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "耳机"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "brand": "AudioMaster"
          }
        },
        {
          "range": {
            "price": {
              "lt": 2999
            }
          }
        }
      ]
    }
  }
}

对应的Java代码如下:

/**
 * 查询价格低于2999并且name中包含"耳机"并且品牌为"AudioMaster"的文档
 */
@Test
public void testBool() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("name", "耳机"))
                    .filter(QueryBuilders.termQuery("brand", "AudioMaster"))
                    .filter(QueryBuilders.rangeQuery("price").lt(2999)));
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    process(response);
}

1.5.4 分页+排序查询

需求:查询全部文档,按照价格从低到高,分页为第一页,三条数据

GET /goods/_search
{
  "query": {
    "match_all": {}
  },
  "sort": {
    "price": "ASC"
  },
  "from": 1,
  "size": 3
}

对应的Java代码如下:

/**
 * 查询全部文档,按照价格从低到高,分页为第一页,三条数据
 */
@Test
public void testPageAndSort() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.matchAllQuery())
            .sort("price", SortOrder.ASC)
            .from(0)
            .size(3);
    // 3. 发起请求,获取响应
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    process(response);
}

1.5.5 高亮显示

DSL高亮语法:

GET /goods/_search
{
  "query": {
    "match": {
      "name": "相机"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<em>",
        "post_tags": "</em>"
      }
    }
  }
}

Java客户端实现:

/**
 * 测试高亮
 */
@Test
public void testHighlight() throws IOException {
    // 1. 创建request
    SearchRequest request = new SearchRequest("goods");
    // 2. 设置查询条件
    request.source()
            .query(QueryBuilders.matchQuery("name", "相机"));
    // 3. 设置高亮条件
    request.source().highlighter(new HighlightBuilder()
            .field("name")
            .preTags("<em>")
            .postTags("</em>"));
    // 4. 进行查询
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 5. 进行解析
    SearchHits hits = response.getHits();
    SearchHit[] searchHits = hits.getHits();
    for (SearchHit searchHit : searchHits) {
        Map<String, HighlightField> hlfs = searchHit.getHighlightFields();
        HighlightField hlf = hlfs.get("name");
        String value = hlf.getFragments()[0].string();
        System.out.println(value);
    }
}

1.5.6 聚合

1.5.6.1 聚合分类

在DSL中,聚合可以分为如下几类:

  1. 桶(Buckets)聚合
    1. Terms Aggregation: 词条聚合
    2. Date 日期时间聚合
  2. 度量(Metric)聚合
    1. min:最小值
    2. max:最大值
    3. avg:平均值
    4. stats:统计最小值、最大值、平均值
  3. 管道(Pipeline)聚合

基于别的聚合再次聚合

1.5.6.2 聚合DSL语句

聚合三要素:

  1. 聚合名称
  2. 聚合类型
  3. 聚合字段

聚合DSL语法:

GET /goods/_search
{
  "size": 0,
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brand",
        "size": 5
      }
    }
  }
}
1.5.6.3 Java客户端操作聚合DSL语句
/**
 * 测试聚合DSL
 */
@Test
public void testAggregation() throws IOException {
    // 1. 创建request对象
    SearchRequest request = new SearchRequest("goods");
    // 2. 编写查询条件
    request.source().query(QueryBuilders.matchAllQuery());
    // 2.1 设置分页
    request.source().size(0);
    // 2.2 设置聚合三要素
    request.source()
            .aggregation(AggregationBuilders
                    .terms("brand_agg")
                    .field("brand")
                    .size(5));
    // 3. 查询结果
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    // 4. 解析结果
    Aggregations aggregations = response.getAggregations();
    Terms terms = aggregations.get("brand_agg");
    List<? extends Terms.Bucket> buckets = terms.getBuckets();
    for (Terms.Bucket bucket : buckets) {
        System.out.println("key: " + bucket.getKeyAsString());
        System.out.println("count: " + bucket.getDocCount());
    }
}
  • 24
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值