当前位置:   article > 正文

基于SpringBoot的ElasticSearch操作(超详细教程)_springboot elasticsearch

springboot elasticsearch

一、ElasticSearch 简介

1、简介


ElasticSearch 是一个基于 Lucene 的搜索服务器。它提供了一个分布式多员工能力的全文搜索引擎,基于 RESTful web 接口。Elasticsearch 是用 Java 语言开发的,并作为 Apache 许可条款下的开放源码发布,是一种流行的企业级搜索引擎。

ElasticSearch 用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

2、特性

分布式的文档存储引擎
分布式的搜索引擎和分析引擎
分布式,支持PB级数据

3、使用场景


搜索领域:如百度、谷歌,全文检索等。
门户网站:访问统计、文章点赞、留言评论等。
广告推广:记录员工行为数据、消费趋势、员工群体进行定制推广等。
信息采集:记录应用的埋点数据、访问日志数据等,方便大数据进行分析。

二、ElasticSearch 基础概念

1、ElaticSearch 和 DB 的关系

在 Elasticsearch 中,文档归属于一种类型 type,而这些类型存在于索引 index 中,我们可以列一些简单的不同点,来类比传统关系型数据库:

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch 集群可以包含多个索引 indices,每一个索引可以包含多个类型 types,每一个类型包含多个文档 documents,然后每个文档包含多个字段 Fields。而在 DB 中可以有多个数据库 Databases,每个库中可以有多张表 Tables,没个表中又包含多行Rows,每行包含多列Columns。

2、索引

索引基本概念(indices):

索引是含义相同属性的文档集合,是 ElasticSearch 的一个逻辑存储,可以理解为关系型数据库中的数据库,ElasticSearch 可以把索引数据存放到一台服务器上,也可以 sharding 后存到多台服务器上,每个索引有一个或多个分片,每个分片可以有多个副本。

索引类型(index_type):

索引可以定义一个或多个类型,文档必须属于一个类型。在 ElasticSearch 中,一个索引对象可以存储多个不同用途的对象,通过索引类型可以区分单个索引中的不同对象,可以理解为关系型数据库中的表。每个索引类型可以有不同的结构,但是不同的索引类型不能为相同的属性设置不同的类型。

3、文档

文档(document):

文档是可以被索引的基本数据单位。存储在 ElasticSearch 中的主要实体叫文档 document,可以理解为关系型数据库中表的一行记录。每个文档由多个字段构成,ElasticSearch 是一个非结构化的数据库,每个文档可以有不同的字段,并且有一个唯一的标识符。

4、映射

映射(mapping):

ElasticSearch 的 Mapping 非常类似于静态语言中的数据类型:声明一个变量为 int 类型的变量,以后这个变量都只能存储 int 类型的数据。同样的,一个 number 类型的 mapping 字段只能存储 number 类型的数据。

同语言的数据类型相比,Mapping 还有一些其他的含义,Mapping 不仅告诉 ElasticSearch 一个 Field 中是什么类型的值, 它还告诉 ElasticSearch 如何索引数据以及数据是否能被搜索到。

ElaticSearch 默认是动态创建索引和索引类型的 Mapping 的。这就相当于无需定义 Solr 中的 Schema,无需指定各个字段的索引规则就可以索引文件,很方便。但有时方便就代表着不灵活。比如,ElasticSearch 默认一个字段是要做分词的,但我们有时要搜索匹配整个字段却不行。如有统计工作要记录每个城市出现的次数。对于 name 字段,若记录 new york 文本,ElasticSearch 可能会把它拆分成 new 和 york 这两个词,分别计算这个两个单词的次数,而不是我们期望的 new york。

三、SpringBoot 项目引入 ElasticSearch 依赖

下面介绍下 SpringBoot 如何通过 elasticsearch-rest-high-level-client 工具操作 ElasticSearch,这里需要说一下,为什么没有使用 Spring 家族封装的 spring-data-elasticsearch。

主要原因是灵活性和更新速度,Spring 将 ElasticSearch 过度封装,让开发者很难跟 ES 的 DSL 查询语句进行关联。再者就是更新速度,ES 的更新速度是非常快,但是 spring-data-elasticsearch 更新速度比较缓慢。

由于上面两点,所以选择了官方推出的 Java 客户端 elasticsearch-rest-high-level-client,它的代码写法跟 DSL 语句很相似,懂 ES 查询的使用其上手很快。

【注意SpringBoot的版本-es的版本对应】

1、Maven 引入相关依赖

  • lombok:lombok 工具依赖。
  • fastjson:用于将 JSON 转换对象的依赖。
  • spring-boot-starter-web: SpringBoot 的 Web 依赖。
  • elasticsearch:ElasticSearch:依赖,需要和 ES 版本保持一致。
  • elasticsearch-rest-high-level-client:用于操作 ES 的 Java 客户端。
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.example</groupId>
  6. <artifactId>elasticsearch</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <name>elasticsearch</name>
  9. <description>Demo project for Spring Boot</description>
  10. <properties>
  11. <java.version>1.8</java.version>
  12. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  13. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  14. <spring-boot.version>2.3.12.RELEASE</spring-boot.version>
  15. </properties>
  16. <dependencies>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-web</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-test</artifactId>
  24. <scope>test</scope>
  25. <exclusions>
  26. <exclusion>
  27. <groupId>org.junit.vintage</groupId>
  28. <artifactId>junit-vintage-engine</artifactId>
  29. </exclusion>
  30. </exclusions>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.projectlombok</groupId>
  34. <artifactId>lombok</artifactId>
  35. <optional>true</optional>
  36. </dependency>
  37. <!--fastjson-->
  38. <dependency>
  39. <groupId>com.alibaba</groupId>
  40. <artifactId>fastjson</artifactId>
  41. <version>1.2.61</version>
  42. </dependency>
  43. <!--elasticsearch-->
  44. <dependency>
  45. <groupId>org.elasticsearch.client</groupId>
  46. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  47. <version>7.6.1</version>
  48. </dependency>
  49. <dependency>
  50. <groupId>org.elasticsearch</groupId>
  51. <artifactId>elasticsearch</artifactId>
  52. <version>7.6.1</version>
  53. </dependency>
  54. </dependencies>
  55. <dependencyManagement>
  56. <dependencies>
  57. <dependency>
  58. <groupId>org.springframework.boot</groupId>
  59. <artifactId>spring-boot-dependencies</artifactId>
  60. <version>${spring-boot.version}</version>
  61. <type>pom</type>
  62. <scope>import</scope>
  63. </dependency>
  64. </dependencies>
  65. </dependencyManagement>
  66. <build>
  67. <plugins>
  68. <plugin>
  69. <groupId>org.apache.maven.plugins</groupId>
  70. <artifactId>maven-compiler-plugin</artifactId>
  71. <version>3.8.1</version>
  72. <configuration>
  73. <source>1.8</source>
  74. <target>1.8</target>
  75. <encoding>UTF-8</encoding>
  76. </configuration>
  77. </plugin>
  78. <plugin>
  79. <groupId>org.springframework.boot</groupId>
  80. <artifactId>spring-boot-maven-plugin</artifactId>
  81. <version>${spring-boot.version}</version>
  82. <configuration>
  83. <mainClass>com.example.elasticsearch.ElasticsearchApplication</mainClass>
  84. <skip>true</skip>
  85. </configuration>
  86. <executions>
  87. <execution>
  88. <id>repackage</id>
  89. <goals>
  90. <goal>repackage</goal>
  91. </goals>
  92. </execution>
  93. </executions>
  94. </plugin>
  95. </plugins>
  96. </build>
  97. </project>

2、ElasticSearch 连接配置

(1)、application.yml 配置文件

为了方便更改连接 ES 的连接配置,所以我们将配置信息放置于 application.yml 中:

  1. server:
  2. port: 8080
  3. servlet:
  4. context-path: /search
  5. elasticsearch:
  6. schema: http
  7. address: 127.0.0.1:9200
  8. connectTimeout: 10000
  9. socketTimeout: 10000
  10. connectionRequestTimeout: 10000
  11. maxConnectNum: 100
  12. maxConnectPerRoute: 100
  13. myindex: testindex

(2)、java 连接配置类

这里需要写一个 Java 配置类读取 application 中的配置信息:

  1. package com.example.elasticsearch.demos.config;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.client.RestClient;
  4. import org.elasticsearch.client.RestClientBuilder;
  5. import org.elasticsearch.client.RestHighLevelClient;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.context.annotation.Bean;
  8. import org.springframework.context.annotation.Configuration;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. /**
  12. * ElasticSearch 配置
  13. */
  14. @Configuration
  15. public class ElasticSearchConfig {
  16. /** 协议 */
  17. @Value("${elasticsearch.schema:http}")
  18. private String schema;
  19. /** 集群地址,如果有多个用“,”隔开 */
  20. @Value("${elasticsearch.address}")
  21. private String address;
  22. /** 连接超时时间 */
  23. @Value("${elasticsearch.connectTimeout:5000}")
  24. private int connectTimeout;
  25. /** Socket 连接超时时间 */
  26. @Value("${elasticsearch.socketTimeout:10000}")
  27. private int socketTimeout;
  28. /** 获取连接的超时时间 */
  29. @Value("${elasticsearch.connectionRequestTimeout:5000}")
  30. private int connectionRequestTimeout;
  31. /** 最大连接数 */
  32. @Value("${elasticsearch.maxConnectNum:100}")
  33. private int maxConnectNum;
  34. /** 最大路由连接数 */
  35. @Value("${elasticsearch.maxConnectPerRoute:100}")
  36. private int maxConnectPerRoute;
  37. @Bean
  38. public RestHighLevelClient restHighLevelClient() {
  39. // 拆分地址
  40. List<HttpHost> hostLists = new ArrayList<>();
  41. String[] hostList = address.split(",");
  42. for (String addr : hostList) {
  43. String host = addr.split(":")[0];
  44. String port = addr.split(":")[1];
  45. hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
  46. }
  47. // 转换成 HttpHost 数组
  48. HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
  49. // 构建连接对象
  50. RestClientBuilder builder = RestClient.builder(httpHost);
  51. // 异步连接延时配置
  52. builder.setRequestConfigCallback(requestConfigBuilder -> {
  53. requestConfigBuilder.setConnectTimeout(connectTimeout);
  54. requestConfigBuilder.setSocketTimeout(socketTimeout);
  55. requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
  56. return requestConfigBuilder;
  57. });
  58. // 异步连接数配置
  59. builder.setHttpClientConfigCallback(httpClientBuilder -> {
  60. httpClientBuilder.setMaxConnTotal(maxConnectNum);
  61. httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
  62. return httpClientBuilder;
  63. });
  64. return new RestHighLevelClient(builder);
  65. }
  66. }

四、索引操作示例

这里示例会指出通过Postman的 ​ ​Restful​​​ 工具操作与对应的 Java 代码操作的两个示例。

1、Restful 操作示例

创建索引

创建名为 testindex 的索引与对应 Mapping。

  1. PUT http://localhost:9200/testindex
  2. {
  3. "mappings": {
  4. "doc": {
  5. "dynamic": true,
  6. "properties": {
  7. "name": {
  8. "type": "text",
  9. "fields": {
  10. "keyword": {
  11. "type": "keyword"
  12. }
  13. }
  14. },
  15. "address": {
  16. "type": "text",
  17. "fields": {
  18. "keyword": {
  19. "type": "keyword"
  20. }
  21. }
  22. },
  23. "remark": {
  24. "type": "text",
  25. "fields": {
  26. "keyword": {
  27. "type": "keyword"
  28. }
  29. }
  30. },
  31. "age": {
  32. "type": "integer"
  33. },
  34. "salary": {
  35. "type": "float"
  36. },
  37. "birthDate": {
  38. "type": "date",
  39. "format": "yyyy-MM-dd"
  40. },
  41. "createTime": {
  42. "type": "date"
  43. }
  44. }
  45. }
  46. }
  47. }

删除索引

删除 mydlq-user 索引。

DELETE http://localhost:9200/testindex

2、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.base;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
  4. import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
  5. import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
  6. import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
  7. import org.elasticsearch.action.support.master.AcknowledgedResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.common.settings.Settings;
  11. import org.elasticsearch.common.xcontent.XContentBuilder;
  12. import org.elasticsearch.common.xcontent.XContentFactory;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.stereotype.Service;
  15. import java.io.IOException;
  16. /**
  17. * 索引操作
  18. */
  19. @Slf4j
  20. @Service
  21. public class IndexService {
  22. @Autowired
  23. private RestHighLevelClient restHighLevelClient;
  24. /**
  25. * 验证索引是否存在
  26. */
  27. public Object existsIndex(String indexName) {
  28. Object result = "";
  29. try {
  30. // 获取索引请求
  31. GetIndexRequest request = new GetIndexRequest();
  32. // 设置要查询的索引名称
  33. request.indices(indexName);
  34. // 执行请求,验证索引是否存在
  35. boolean isExist = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
  36. log.info("是否存在:{}", isExist);
  37. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  38. result = isExist;
  39. } catch (IOException e) {
  40. log.error("", e);
  41. }
  42. return result;
  43. }
  44. /**
  45. * 创建索引
  46. */
  47. public Object createIndex(String indexName) {
  48. Object result = "";
  49. try {
  50. // 创建 Mapping
  51. XContentBuilder mapping = XContentFactory.jsonBuilder()
  52. .startObject()
  53. .field("dynamic", true)
  54. .startObject("properties")
  55. .startObject("name")
  56. .field("type","text")
  57. .startObject("fields")
  58. .startObject("keyword")
  59. .field("type","keyword")
  60. .endObject()
  61. .endObject()
  62. .endObject()
  63. .startObject("address")
  64. .field("type","text")
  65. .startObject("fields")
  66. .startObject("keyword")
  67. .field("type","keyword")
  68. .endObject()
  69. .endObject()
  70. .endObject()
  71. .startObject("remark")
  72. .field("type","text")
  73. .startObject("fields")
  74. .startObject("keyword")
  75. .field("type","keyword")
  76. .endObject()
  77. .endObject()
  78. .endObject()
  79. .startObject("age")
  80. .field("type","integer")
  81. .endObject()
  82. .startObject("salary")
  83. .field("type","float")
  84. .endObject()
  85. .startObject("birthDate")
  86. .field("type","date")
  87. .field("format", "yyyy-MM-dd")
  88. .endObject()
  89. .startObject("createTime")
  90. .field("type","date")
  91. .endObject()
  92. .endObject()
  93. .endObject();
  94. // 创建索引配置信息,配置
  95. Settings settings = Settings.builder()
  96. .put("index.number_of_shards", 1)
  97. .put("index.number_of_replicas", 0)
  98. .build();
  99. // 新建创建索引请求对象,然后设置索引类型(ES 7.0 将不存在索引类型)和 mapping 与 index 配置
  100. CreateIndexRequest request = new CreateIndexRequest(indexName, settings);
  101. request.mapping("doc", mapping);
  102. // RestHighLevelClient 执行创建索引
  103. CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
  104. // 判断是否创建成功
  105. boolean isCreated = createIndexResponse.isAcknowledged();
  106. log.info("是否创建成功:{}", isCreated);
  107. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  108. result = isCreated;
  109. } catch (IOException e) {
  110. log.error("", e);
  111. }
  112. return result;
  113. }
  114. /**
  115. * 删除索引
  116. */
  117. public Object deleteIndex(String indexName) {
  118. Object result = "";
  119. try {
  120. // 新建删除索引请求对象
  121. DeleteIndexRequest request = new DeleteIndexRequest(indexName);
  122. // 执行删除索引
  123. AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
  124. // 判断是否删除成功
  125. boolean siDeleted = acknowledgedResponse.isAcknowledged();
  126. log.info("是否删除成功:{}", siDeleted);
  127. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  128. result = siDeleted;
  129. } catch (IOException e) {
  130. log.error("", e);
  131. }
  132. return result;
  133. }
  134. }

五、文档操作示例

1、Restful 操作示例

增加文档信息

在索引 mydlq-user 中增加一条文档信息。

  1. POST http://localhost:9200/testindex/doc
  2. {
  3. "address": "北京市",
  4. "age": 29,
  5. "birthDate": "1990-01-10",
  6. "createTime": 1579530727699,
  7. "name": "张三",
  8. "remark": "来自北京市的张先生",
  9. "salary": 100
  10. }
  11. //返回
  12. {
  13. "_index": "testindex",
  14. "_type": "doc",
  15. "_id": "hZo5_4oBFE0BmNy_GMUN", //这个是插入生成的随机id
  16. "_version": 1,
  17. "result": "created",
  18. "_shards": {
  19. "total": 1,
  20. "successful": 1,
  21. "failed": 0
  22. },
  23. "_seq_no": 29,
  24. "_primary_term": 3
  25. }

获取文档信息

获取 testindex的索引 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

  1. GET http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN
  2. //返回
  3. {
  4. "_index": "testindex",
  5. "_type": "doc",
  6. "_id": "hZo5_4oBFE0BmNy_GMUN",
  7. "_version": 1,
  8. "_seq_no": 29,
  9. "_primary_term": 3,
  10. "found": true,
  11. "_source": {
  12. "address": "北京市",
  13. "age": 29,
  14. "birthDate": "1990-01-10",
  15. "createTime": 1579530727699,
  16. "name": "张三",
  17. "remark": "来自北京市的张先生",
  18. "salary": 100
  19. }
  20. }

更新文档信息

更新之前创建的 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

  1. PUT http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN
  2. //请求
  3. {
  4. "address": "北京市",
  5. "age": 29,
  6. "birthDate": "1990-01-10",
  7. "createTime": 1579530727699,
  8. "name": "张三(改名字)",
  9. "remark": "来自北京市的张先生",
  10. "salary": 100
  11. }

删除文档信息

删除之前创建的 id=hZo5_4oBFE0BmNy_GMUN 的文档信息。

DELETE http://localhost:9200/testindex/doc/hZo5_4oBFE0BmNy_GMUN

2、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.base;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.DocDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.delete.DeleteRequest;
  7. import org.elasticsearch.action.delete.DeleteResponse;
  8. import org.elasticsearch.action.get.GetRequest;
  9. import org.elasticsearch.action.get.GetResponse;
  10. import org.elasticsearch.action.index.IndexRequest;
  11. import org.elasticsearch.action.index.IndexResponse;
  12. import org.elasticsearch.action.update.UpdateRequest;
  13. import org.elasticsearch.action.update.UpdateResponse;
  14. import org.elasticsearch.client.RequestOptions;
  15. import org.elasticsearch.client.RestHighLevelClient;
  16. import org.elasticsearch.common.xcontent.XContentType;
  17. import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.stereotype.Service;
  20. import java.io.IOException;
  21. import java.util.Date;
  22. /**
  23. * 文档操作
  24. */
  25. @Slf4j
  26. @Service
  27. public class DocumentService {
  28. @Autowired
  29. private RestHighLevelClient restHighLevelClient;
  30. public Object existsDocument(DocDto docDto) {
  31. Object result = "";
  32. try {
  33. // 获取请求对象
  34. GetRequest getRequest = new GetRequest(docDto.getIndexName(), docDto.getDocId());
  35. // 是否获取源码内容
  36. getRequest.fetchSourceContext(new FetchSourceContext(false));
  37. // 执行请求,验证文档是否存在
  38. boolean isExist = restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
  39. log.info("文档是否存在:{}", isExist);
  40. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  41. result = isExist;
  42. } catch (IOException e) {
  43. log.error("", e);
  44. }
  45. return result;
  46. }
  47. public Object getDocument(DocDto docDto) {
  48. Object result = "";
  49. try {
  50. // 获取请求对象
  51. GetRequest getRequest = new GetRequest(docDto.getIndexName(), docDto.getDocId());
  52. // 获取文档信息
  53. GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
  54. // 将 JSON 转换成对象
  55. if (getResponse.isExists()) {
  56. UserInfo userInfo = JSON.parseObject(getResponse.getSourceAsBytes(), UserInfo.class);
  57. log.info("用户信息:{}", userInfo);
  58. }
  59. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  60. result = getResponse;
  61. } catch (IOException e) {
  62. log.error("", e);
  63. }
  64. return result;
  65. }
  66. public Object addDocument(DocDto docDto) {
  67. Object result = "";
  68. try {
  69. // 创建索引请求对象
  70. IndexRequest indexRequest = new IndexRequest(docDto.getIndexName());
  71. // 创建用户信息
  72. UserInfo userInfo = new UserInfo();
  73. userInfo.setName(docDto.getName());
  74. userInfo.setAge(docDto.getAge());
  75. userInfo.setSalary(docDto.getSalary());
  76. userInfo.setAddress(docDto.getAddress());
  77. userInfo.setRemark(docDto.getRemark());
  78. userInfo.setCreateTime(new Date());
  79. userInfo.setBirthDate(docDto.getBirthDate());
  80. // 将对象转换为 byte 数组
  81. byte[] json = JSON.toJSONBytes(userInfo);
  82. // 设置文档内容
  83. indexRequest.source(json, XContentType.JSON);
  84. // 执行增加文档
  85. IndexResponse response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
  86. log.info("创建状态:{}", response.status());
  87. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  88. result = response;
  89. } catch (Exception e) {
  90. log.error("", e);
  91. }
  92. return result;
  93. }
  94. public Object updateDocument(DocDto docDto) {
  95. Object result = "";
  96. try {
  97. // 创建索引请求对象
  98. UpdateRequest updateRequest = new UpdateRequest(docDto.getIndexName(), docDto.getDocId());
  99. // UpdateRequest updateRequest = new UpdateRequest(docDto.getIndexName(), "doc", docDto.getDocId());
  100. // 设置用户更新信息
  101. UserInfo userInfo = new UserInfo();
  102. userInfo.setSalary(docDto.getSalary());
  103. userInfo.setAddress(docDto.getAddress());
  104. // 将对象转换为 byte 数组
  105. byte[] json = JSON.toJSONBytes(userInfo);
  106. // 设置更新文档内容
  107. updateRequest.doc(json, XContentType.JSON);
  108. // 执行更新文档
  109. UpdateResponse response = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
  110. log.info("创建状态:{}", response.status());
  111. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  112. result = response;
  113. } catch (Exception e) {
  114. log.error("", e);
  115. }
  116. return result;
  117. }
  118. public Object deleteDocument(DocDto docDto) {
  119. Object result = "";
  120. try {
  121. // 创建删除请求对象
  122. DeleteRequest deleteRequest = new DeleteRequest(docDto.getIndexName(), docDto.getDocId());
  123. // 执行删除文档
  124. DeleteResponse response = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
  125. log.info("删除状态:{}", response.status());
  126. // 根据具体业务逻辑返回不同结果,这里为了方便直接将结果返回
  127. result = response;
  128. } catch (IOException e) {
  129. log.error("", e);
  130. }
  131. return result;
  132. }
  133. }

六、插入初始化数据

执行查询示例前,先往索引中插入一批数据:

1、单条插入

  1. POST http://localhost:9200/testindex/doc
  2. //请求
  3. {
  4. "name": "零零",
  5. "address": "北京市丰台区",
  6. "remark": "低层员工",
  7. "age": 29,
  8. "salary": 3000,
  9. "birthDate": "1990-11-11",
  10. "createTime": "2019-11-11T08:18:00.000Z"
  11. }

2、批量插入

  1. POST http://localhost:9200/_bulk
  2. //header
  3. Content-Type: application/json
  4. //body
  5. {"index":{"_index":"testindex","_type":"doc"}}
  6. {"name":"刘一","address":"北京市丰台区","remark":"低层员工","age":30,"salary":3000,"birthDate":"1989-11-11","createTime":"2019-03-15T08:18:00.000Z"}
  7. {"index":{"_index":"testindex","_type":"doc"}}
  8. {"name":"陈二","address":"北京市昌平区","remark":"中层员工","age":27,"salary":7900,"birthDate":"1992-01-25","createTime":"2019-11-08T11:15:00.000Z"}
  9. {"index":{"_index":"testindex","_type":"doc"}}
  10. {"name":"张三","address":"北京市房山区","remark":"中层员工","age":28,"salary":8800,"birthDate":"1991-10-05","createTime":"2019-07-22T13:22:00.000Z"}
  11. {"index":{"_index":"testindex","_type":"doc"}}
  12. {"name":"李四","address":"北京市大兴区","remark":"高层员工","age":26,"salary":9000,"birthDate":"1993-08-18","createTime":"2019-10-17T15:00:00.000Z"}
  13. {"index":{"_index":"testindex","_type":"doc"}}
  14. {"name":"王五","address":"北京市密云区","remark":"低层员工","age":31,"salary":4800,"birthDate":"1988-07-20","createTime":"2019-05-29T09:00:00.000Z"}
  15. {"index":{"_index":"testindex","_type":"doc"}}
  16. {"name":"赵六","address":"北京市通州区","remark":"中层员工","age":32,"salary":6500,"birthDate":"1987-06-02","createTime":"2019-12-10T18:00:00.000Z"}
  17. {"index":{"_index":"testindex","_type":"doc"}}
  18. {"name":"孙七","address":"北京市朝阳区","remark":"中层员工","age":33,"salary":7000,"birthDate":"1986-04-15","createTime":"2019-06-06T13:00:00.000Z"}
  19. {"index":{"_index":"testindex","_type":"doc"}}
  20. {"name":"周八","address":"北京市西城区","remark":"低层员工","age":32,"salary":5000,"birthDate":"1987-09-26","createTime":"2019-01-26T14:00:00.000Z"}
  21. {"index":{"_index":"testindex","_type":"doc"}}
  22. {"name":"吴九","address":"北京市海淀区","remark":"高层员工","age":30,"salary":11000,"birthDate":"1989-11-25","createTime":"2019-09-07T13:34:00.000Z"}
  23. {"index":{"_index":"testindex","_type":"doc"}}
  24. {"name":"郑十","address":"北京市东城区","remark":"低层员工","age":29,"salary":5000,"birthDate":"1990-12-25","createTime":"2019-03-06T12:08:00.000Z"}
  25. {"index":{"_index":"testindex","_type":"doc"}}
  26. {"name":"萧十一","address":"北京市平谷区","remark":"低层员工","age":29,"salary":3300,"birthDate":"1990-11-11","createTime":"2019-03-10T08:17:00.000Z"}
  27. {"index":{"_index":"testindex","_type":"doc"}}
  28. {"name":"曹十二","address":"北京市怀柔区","remark":"中层员工","age":27,"salary":6800,"birthDate":"1992-01-25","createTime":"2019-12-03T11:09:00.000Z"}
  29. {"index":{"_index":"testindex","_type":"doc"}}
  30. {"name":"吴十三","address":"北京市延庆区","remark":"中层员工","age":25,"salary":7000,"birthDate":"1994-10-05","createTime":"2019-07-27T14:22:00.000Z"}
  31. {"index":{"_index":"testindex","_type":"doc"}}
  32. {"name":"冯十四","address":"北京市密云区","remark":"低层员工","age":25,"salary":3000,"birthDate":"1994-08-18","createTime":"2019-04-22T15:00:00.000Z"}
  33. {"index":{"_index":"testindex","_type":"doc"}}
  34. {"name":"蒋十五","address":"北京市通州区","remark":"低层员工","age":31,"salary":2800,"birthDate":"1988-07-20","createTime":"2019-06-13T10:00:00.000Z"}
  35. {"index":{"_index":"testindex","_type":"doc"}}
  36. {"name":"苗十六","address":"北京市门头沟区","remark":"高层员工","age":32,"salary":11500,"birthDate":"1987-06-02","createTime":"2019-11-11T18:00:00.000Z"}
  37. {"index":{"_index":"testindex","_type":"doc"}}
  38. {"name":"鲁十七","address":"北京市石景山区","remark":"高员工","age":33,"salary":9500,"birthDate":"1986-04-15","createTime":"2019-06-06T14:00:00.000Z"}
  39. {"index":{"_index":"testindex","_type":"doc"}}
  40. {"name":"沈十八","address":"北京市朝阳区","remark":"中层员工","age":31,"salary":8300,"birthDate":"1988-09-26","createTime":"2019-09-25T14:00:00.000Z"}
  41. {"index":{"_index":"testindex","_type":"doc"}}
  42. {"name":"吕十九","address":"北京市西城区","remark":"低层员工","age":31,"salary":4500,"birthDate":"1988-11-25","createTime":"2019-09-22T13:34:00.000Z"}
  43. {"index":{"_index":"testindex","_type":"doc"}}
  44. {"name":"丁二十","address":"北京市东城区","remark":"低层员工","age":33,"salary":2100,"birthDate":"1986-12-25","createTime":"2019-03-07T12:08:00.000Z"}

3、查询数据

插入完成后再查询数据,查看之前插入的数据是否存在:

  1. GET http://localhost:9200/testindex/_search
  2. //返回
  3. {
  4. "took": 6,
  5. "timed_out": false,
  6. "_shards": {
  7. "total": 1,
  8. "successful": 1,
  9. "skipped": 0,
  10. "failed": 0
  11. },
  12. "hits": {
  13. "total": {
  14. "value": 2,
  15. "relation": "eq"
  16. },
  17. "max_score": 2.302585,
  18. "hits": [
  19. {
  20. "_index": "testindex",
  21. "_type": "doc",
  22. "_id": "3iDh-IoByPOFA_QWinlo",
  23. "_score": 2.302585,
  24. "_source": {
  25. "name": "赵六",
  26. "address": "北京市通州区",
  27. "remark": "中层员工",
  28. "age": 32,
  29. "salary": 6500,
  30. "birthDate": "1987-06-02",
  31. "createTime": "2019-12-10T18:00:00.000Z"
  32. }
  33. },
  34. {
  35. "_index": "testindex",
  36. "_type": "doc",
  37. "_id": "5yDh-IoByPOFA_QWinlo",
  38. "_score": 2.302585,
  39. "_source": {
  40. "name": "蒋十五",
  41. "address": "北京市通州区",
  42. "remark": "低层员工",
  43. "age": 31,
  44. "salary": 2800,
  45. "birthDate": "1988-07-20",
  46. "createTime": "2019-06-13T10:00:00.000Z"
  47. }
  48. }
  49. ...
  50. ]
  51. }
  52. }

七、查询操作示例

1、精确查询(term)

(1)、Restful 操作示例

精确查询

精确查询,查询地址为 北京市通州区 的人员信息:

查询条件不会进行分词,但是查询内容可能会分词,导致查询不到。之前在创建索引时设置 Mapping 中 address 字段存在 keyword 字段是专门用于不分词查询的子字段。

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "term": {
  6. "address.keyword": {
  7. "value": "北京市通州区"
  8. }
  9. }
  10. }
  11. }
  12. //返回
  13. {
  14. "took": 6,
  15. "timed_out": false,
  16. "_shards": {
  17. "total": 1,
  18. "successful": 1,
  19. "skipped": 0,
  20. "failed": 0
  21. },
  22. "hits": {
  23. "total": {
  24. "value": 2,
  25. "relation": "eq"
  26. },
  27. "max_score": 2.302585,
  28. "hits": [
  29. {
  30. "_index": "testindex",
  31. "_type": "doc",
  32. "_id": "3iDh-IoByPOFA_QWinlo",
  33. "_score": 2.302585,
  34. "_source": {
  35. "name": "赵六",
  36. "address": "北京市通州区",
  37. "remark": "中层员工",
  38. "age": 32,
  39. "salary": 6500,
  40. "birthDate": "1987-06-02",
  41. "createTime": "2019-12-10T18:00:00.000Z"
  42. }
  43. },
  44. {
  45. "_index": "testindex",
  46. "_type": "doc",
  47. "_id": "5yDh-IoByPOFA_QWinlo",
  48. "_score": 2.302585,
  49. "_source": {
  50. "name": "蒋十五",
  51. "address": "北京市通州区",
  52. "remark": "低层员工",
  53. "age": 31,
  54. "salary": 2800,
  55. "birthDate": "1988-07-20",
  56. "createTime": "2019-06-13T10:00:00.000Z"
  57. }
  58. }
  59. ...
  60. ]
  61. }
  62. }
精确查询-多内容查询

精确查询,查询地址为 北京市丰台区、北京市昌平区 或 北京市大兴区 的人员信息:

 

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "terms": {
  6. "address.keyword": [
  7. "北京市丰台区",
  8. "北京市昌平区",
  9. "北京市大兴区"
  10. ]
  11. }
  12. }
  13. }

(2)、Java 代码示例 

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.TermsQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.QueryBuilders;
  11. import org.elasticsearch.rest.RestStatus;
  12. import org.elasticsearch.search.SearchHit;
  13. import org.elasticsearch.search.SearchHits;
  14. import org.elasticsearch.search.builder.SearchSourceBuilder;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Service;
  17. import java.io.IOException;
  18. import java.util.Arrays;
  19. /**
  20. * 精确查询
  21. */
  22. @Slf4j
  23. @Service
  24. public class TermQueryService {
  25. @Autowired
  26. private RestHighLevelClient restHighLevelClient;
  27. /**
  28. * 精确查询(查询条件不会进行分词,但是查询内容可能会分词,导致查询不到)
  29. * @param queryDto
  30. */
  31. public Object termQuery(TermsQueryDto queryDto) {
  32. Object result = "";
  33. try {
  34. // 构建查询条件(注意:termQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
  35. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  36. searchSourceBuilder.query(QueryBuilders.termQuery(queryDto.getKey() + ".keyword", queryDto.getValue()));
  37. // 创建查询请求对象,将查询对象配置到其中
  38. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  39. searchRequest.source(searchSourceBuilder);
  40. // 执行查询,然后处理响应结果
  41. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  42. // 根据状态和数据条数验证是否返回了数据
  43. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  44. SearchHits hits = searchResponse.getHits();
  45. for (SearchHit hit : hits) {
  46. // 将 JSON 转换成对象
  47. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  48. // 输出查询信息
  49. log.info(userInfo.toString());
  50. }
  51. }
  52. result = searchResponse.getHits();
  53. } catch (IOException e) {
  54. log.error("", e);
  55. }
  56. return result;
  57. }
  58. /**
  59. * 多个内容在一个字段中进行查询
  60. * @param queryDto
  61. */
  62. public Object termsQuery(TermsQueryDto queryDto) {
  63. Object result = "";
  64. try {
  65. // 构建查询条件(注意:termsQuery 支持多种格式查询,如 boolean、int、double、string 等,这里使用的是 string 的查询)
  66. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  67. searchSourceBuilder.query(QueryBuilders.termsQuery(queryDto.getKey() + ".keyword", queryDto.getValues()));
  68. // 创建查询请求对象,将查询对象配置到其中
  69. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  70. searchRequest.source(searchSourceBuilder);
  71. // 执行查询,然后处理响应结果
  72. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  73. // 根据状态和数据条数验证是否返回了数据
  74. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  75. SearchHits hits = searchResponse.getHits();
  76. for (SearchHit hit : hits) {
  77. // 将 JSON 转换成对象
  78. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  79. // 输出查询信息
  80. log.info(userInfo.toString());
  81. }
  82. }
  83. result = searchResponse.getHits();
  84. } catch (IOException e) {
  85. log.error("", e);
  86. }
  87. return result;
  88. }
  89. }

2、匹配查询(match)

(1)、Restful 操作示例

匹配查询全部数据与分页

匹配查询符合条件的所有数据,并且设置以 salary 字段升序排序,并设置分页:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "match_all": {}
  6. },
  7. "from": 0,
  8. "size": 10,
  9. "sort": [
  10. {
  11. "salary": {
  12. "order": "asc"
  13. }
  14. }
  15. ]
  16. }
匹配查询数据

匹配查询地址为 通州区 的数据:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "match": {
  6. "address": "通州区"
  7. }
  8. }
  9. }
 词语匹配查询

词语匹配进行查询,匹配 address 中为 北京市通州区 的员工信息:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "match_phrase": {
  6. "address": "北京市通州区"
  7. }
  8. }
  9. }
内容多字段查询

查询在字段 address、remark 中存在 北京 内容的员工信息:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "multi_match": {
  6. "query": "北京",
  7. "fields": ["address","remark"]
  8. }
  9. }
  10. }

 (2)、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.MatchAllQueryBuilder;
  11. import org.elasticsearch.index.query.QueryBuilders;
  12. import org.elasticsearch.rest.RestStatus;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.builder.SearchSourceBuilder;
  16. import org.elasticsearch.search.sort.SortOrder;
  17. import org.springframework.beans.factory.annotation.Autowired;
  18. import org.springframework.stereotype.Service;
  19. import java.io.IOException;
  20. /**
  21. * 匹配查询
  22. */
  23. @Slf4j
  24. @Service
  25. public class MatchQueryService {
  26. @Autowired
  27. private RestHighLevelClient restHighLevelClient;
  28. /**
  29. * 匹配查询符合条件的所有数据,并设置分页
  30. * @param queryDto
  31. */
  32. public Object matchAllQuery(MatchQueryDto queryDto) {
  33. Object result = "";
  34. try {
  35. // 构建查询条件
  36. MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
  37. // 创建查询源构造器
  38. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  39. searchSourceBuilder.query(matchAllQueryBuilder);
  40. // 设置分页
  41. searchSourceBuilder.from((queryDto.getRows() - 1) * queryDto.getSize());
  42. searchSourceBuilder.size(queryDto.getSize());
  43. // 设置排序
  44. searchSourceBuilder.sort("salary", SortOrder.ASC);
  45. // 创建查询请求对象,将查询对象配置到其中
  46. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  47. searchRequest.source(searchSourceBuilder);
  48. // 执行查询,然后处理响应结果
  49. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  50. // 根据状态和数据条数验证是否返回了数据
  51. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  52. SearchHits hits = searchResponse.getHits();
  53. for (SearchHit hit : hits) {
  54. // 将 JSON 转换成对象
  55. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  56. // 输出查询信息
  57. log.info(userInfo.toString());
  58. }
  59. }
  60. result = searchResponse.getHits();
  61. } catch (IOException e) {
  62. log.error("", e);
  63. }
  64. return result;
  65. }
  66. /**
  67. * 匹配查询数据-or的方式
  68. * @param queryDto
  69. */
  70. public Object matchQuery(MatchQueryDto queryDto) {
  71. Object result = "";
  72. try {
  73. // 构建查询条件
  74. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  75. searchSourceBuilder.query(QueryBuilders.matchQuery(queryDto.getKey(), queryDto.getValue()));
  76. // searchSourceBuilder.query(QueryBuilders.matchQuery("address", "通州区"));
  77. // 创建查询请求对象,将查询对象配置到其中
  78. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  79. searchRequest.source(searchSourceBuilder);
  80. // 执行查询,然后处理响应结果
  81. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  82. // 根据状态和数据条数验证是否返回了数据
  83. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  84. SearchHits hits = searchResponse.getHits();
  85. for (SearchHit hit : hits) {
  86. // 将 JSON 转换成对象
  87. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  88. // 输出查询信息
  89. log.info(userInfo.toString());
  90. }
  91. }
  92. result = searchResponse.getHits();
  93. } catch (IOException e) {
  94. log.error("", e);
  95. }
  96. return result;
  97. }
  98. /**
  99. * 词语匹配查询
  100. * @param queryDto
  101. */
  102. public Object matchPhraseQuery(MatchQueryDto queryDto) {
  103. Object result = "";
  104. try {
  105. // 构建查询条件
  106. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  107. searchSourceBuilder.query(QueryBuilders.matchPhraseQuery(queryDto.getKey(), queryDto.getValue()));
  108. // searchSourceBuilder.query(QueryBuilders.matchPhraseQuery("address", "北京市通州区"));
  109. // 创建查询请求对象,将查询对象配置到其中
  110. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  111. searchRequest.source(searchSourceBuilder);
  112. // 执行查询,然后处理响应结果
  113. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  114. // 根据状态和数据条数验证是否返回了数据
  115. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  116. SearchHits hits = searchResponse.getHits();
  117. for (SearchHit hit : hits) {
  118. // 将 JSON 转换成对象
  119. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  120. // 输出查询信息
  121. log.info(userInfo.toString());
  122. }
  123. }
  124. result = searchResponse.getHits();
  125. } catch (IOException e) {
  126. log.error("", e);
  127. }
  128. return result;
  129. }
  130. /**
  131. * 内容在多字段中进行查询
  132. * @param queryDto
  133. */
  134. public Object matchMultiQuery(MatchQueryDto queryDto) {
  135. Object result = "";
  136. try {
  137. // 构建查询条件
  138. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  139. searchSourceBuilder.query(QueryBuilders.multiMatchQuery(queryDto.getKey(), queryDto.getValues()));
  140. // searchSourceBuilder.query(QueryBuilders.multiMatchQuery("北京市", "address", "remark"));
  141. // 创建查询请求对象,将查询对象配置到其中
  142. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  143. searchRequest.source(searchSourceBuilder);
  144. // 执行查询,然后处理响应结果
  145. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  146. // 根据状态和数据条数验证是否返回了数据
  147. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  148. SearchHits hits = searchResponse.getHits();
  149. for (SearchHit hit : hits) {
  150. // 将 JSON 转换成对象
  151. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  152. // 输出查询信息
  153. log.info(userInfo.toString());
  154. }
  155. }
  156. result = searchResponse.getHits();
  157. } catch (IOException e) {
  158. log.error("", e);
  159. }
  160. return result;
  161. }
  162. }

3、模糊查询(fuzzy)

(1)、Restful 操作示例

模糊查询所有以 三 结尾的姓名
  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "fuzzy": {
  6. "name": "三"
  7. }
  8. }
  9. }

 (2)、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.common.unit.Fuzziness;
  11. import org.elasticsearch.index.query.QueryBuilders;
  12. import org.elasticsearch.rest.RestStatus;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.builder.SearchSourceBuilder;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.stereotype.Service;
  18. import java.io.IOException;
  19. /**
  20. * 模糊查询
  21. */
  22. @Slf4j
  23. @Service
  24. public class FuzzyQueryService {
  25. @Autowired
  26. private RestHighLevelClient restHighLevelClient;
  27. /**
  28. * 模糊查询所有以 “三” 结尾的姓名
  29. * @param queryDto
  30. */
  31. public Object fuzzyQuery(MatchQueryDto queryDto) {
  32. Object result = "";
  33. try {
  34. // 构建查询条件
  35. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  36. searchSourceBuilder.query(QueryBuilders.fuzzyQuery(queryDto.getKey(), queryDto.getValue()).fuzziness(Fuzziness.AUTO));
  37. // 创建查询请求对象,将查询对象配置到其中
  38. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  39. searchRequest.source(searchSourceBuilder);
  40. // 执行查询,然后处理响应结果
  41. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  42. // 根据状态和数据条数验证是否返回了数据
  43. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  44. SearchHits hits = searchResponse.getHits();
  45. for (SearchHit hit : hits) {
  46. // 将 JSON 转换成对象
  47. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  48. // 输出查询信息
  49. log.info(userInfo.toString());
  50. }
  51. }
  52. result = searchResponse.getHits();
  53. } catch (IOException e) {
  54. log.error("", e);
  55. }
  56. return result;
  57. }
  58. }

4、范围查询(range)

(1)、Restful 操作示例

查询岁数 ≥ 30 岁的员工数据:
  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "range": {
  6. "age": {
  7. "gte": 30
  8. }
  9. }
  10. }
  11. }
查询生日距离现在 30 年间的员工数据: 
  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "range": {
  6. "birthDate": {
  7. "gte": "now-30y"
  8. }
  9. }
  10. }
  11. }

(2)、Java 代码示例 

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.QueryBuilders;
  11. import org.elasticsearch.rest.RestStatus;
  12. import org.elasticsearch.search.SearchHit;
  13. import org.elasticsearch.search.SearchHits;
  14. import org.elasticsearch.search.builder.SearchSourceBuilder;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Service;
  17. import java.io.IOException;
  18. /**
  19. * 范围查询
  20. */
  21. @Slf4j
  22. @Service
  23. public class RangeQueryService {
  24. @Autowired
  25. private RestHighLevelClient restHighLevelClient;
  26. /**
  27. * 查询岁数 ≥ 30 岁的员工数据
  28. * @param queryDto
  29. */
  30. public Object rangeQuery(MatchQueryDto queryDto) {
  31. Object result = "";
  32. try {
  33. // 构建查询条件
  34. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  35. searchSourceBuilder.query(QueryBuilders.rangeQuery("age").gte(30));
  36. // 创建查询请求对象,将查询对象配置到其中
  37. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  38. searchRequest.source(searchSourceBuilder);
  39. // 执行查询,然后处理响应结果
  40. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  41. // 根据状态和数据条数验证是否返回了数据
  42. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  43. SearchHits hits = searchResponse.getHits();
  44. for (SearchHit hit : hits) {
  45. // 将 JSON 转换成对象
  46. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  47. // 输出查询信息
  48. log.info(userInfo.toString());
  49. }
  50. }
  51. result = searchResponse.getHits();
  52. } catch (IOException e) {
  53. log.error("", e);
  54. }
  55. return result;
  56. }
  57. /**
  58. * 查询距离现在 30 年间的员工数据
  59. * [年(y)、月(M)、星期(w)、天(d)、小时(h)、分钟(m)、秒(s)]
  60. * 例如:
  61. * now-1h 查询一小时内范围
  62. * now-1d 查询一天内时间范围
  63. * now-1y 查询最近一年内的时间范围
  64. * @param queryDto
  65. */
  66. public Object dateRangeQuery(MatchQueryDto queryDto) {
  67. Object result = "";
  68. try {
  69. // 构建查询条件
  70. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  71. // // includeLower(是否包含下边界)、includeUpper(是否包含上边界)
  72. // searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate")
  73. // .gte("now-30y").includeLower(true).includeUpper(true));
  74. searchSourceBuilder.query(QueryBuilders.rangeQuery("birthDate").gte(queryDto.getFrom()).lte(queryDto.getEnd()));
  75. // 创建查询请求对象,将查询对象配置到其中
  76. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  77. searchRequest.source(searchSourceBuilder);
  78. // 执行查询,然后处理响应结果
  79. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  80. // 根据状态和数据条数验证是否返回了数据
  81. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  82. SearchHits hits = searchResponse.getHits();
  83. for (SearchHit hit : hits) {
  84. // 将 JSON 转换成对象
  85. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  86. // 输出查询信息
  87. log.info(userInfo.toString());
  88. }
  89. }
  90. result = searchResponse.getHits();
  91. } catch (IOException e) {
  92. log.error("", e);
  93. }
  94. return result;
  95. }
  96. }

5、通配符查询(wildcard)

(1)、Restful 操作示例

查询所有以 “三” 结尾的姓名:
  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "wildcard": {
  6. "name.keyword": {
  7. "value": "*三"
  8. }
  9. }
  10. }
  11. }

(2)、Java 代码示例 

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.QueryBuilders;
  11. import org.elasticsearch.rest.RestStatus;
  12. import org.elasticsearch.search.SearchHit;
  13. import org.elasticsearch.search.SearchHits;
  14. import org.elasticsearch.search.builder.SearchSourceBuilder;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.stereotype.Service;
  17. import java.io.IOException;
  18. /**
  19. * 通配符查询
  20. */
  21. @Slf4j
  22. @Service
  23. public class WildcardQueryService {
  24. @Autowired
  25. private RestHighLevelClient restHighLevelClient;
  26. /**
  27. * 查询所有以 “三” 结尾的姓名
  28. * <p>
  29. * *:表示多个字符(0个或多个字符)
  30. * ?:表示单个字符
  31. * @param queryDto
  32. */
  33. public Object wildcardQuery(MatchQueryDto queryDto) {
  34. Object result = "";
  35. try {
  36. // 构建查询条件
  37. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  38. searchSourceBuilder.query(QueryBuilders.wildcardQuery(queryDto.getKey() + ".keyword", "*" + queryDto.getValue()));
  39. // 创建查询请求对象,将查询对象配置到其中
  40. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  41. searchRequest.source(searchSourceBuilder);
  42. // 执行查询,然后处理响应结果
  43. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  44. // 根据状态和数据条数验证是否返回了数据
  45. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  46. SearchHits hits = searchResponse.getHits();
  47. for (SearchHit hit : hits) {
  48. // 将 JSON 转换成对象
  49. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  50. // 输出查询信息
  51. log.info(userInfo.toString());
  52. }
  53. }
  54. result = searchResponse.getHits();
  55. } catch (IOException e) {
  56. log.error("", e);
  57. }
  58. return result;
  59. }
  60. }

6、布尔查询(bool)

(1)、Restful 操作示例

查询出生在 1990-1995 年期间,且地址在 北京市昌平区、北京市大兴区、北京市房山区 的员工信息:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "query": {
  5. "bool": {
  6. "filter": {
  7. "range": {
  8. "birthDate": {
  9. "format": "yyyy",
  10. "gte": 1990,
  11. "lte": 1995
  12. }
  13. }
  14. },
  15. "must": [
  16. {
  17. "terms": {
  18. "address.keyword": [
  19. "北京市昌平区",
  20. "北京市大兴区",
  21. "北京市房山区"
  22. ]
  23. }
  24. }
  25. ]
  26. }
  27. }
  28. }

 (2)、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.query;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  4. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.index.query.BoolQueryBuilder;
  11. import org.elasticsearch.index.query.QueryBuilders;
  12. import org.elasticsearch.rest.RestStatus;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.elasticsearch.search.SearchHits;
  15. import org.elasticsearch.search.builder.SearchSourceBuilder;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.stereotype.Service;
  18. import java.io.IOException;
  19. /**
  20. * 布尔查询
  21. */
  22. @Slf4j
  23. @Service
  24. public class BoolQueryService {
  25. @Autowired
  26. private RestHighLevelClient restHighLevelClient;
  27. /**
  28. * 布尔查询
  29. * @param queryDto
  30. */
  31. public Object boolQuery(MatchQueryDto queryDto) {
  32. Object result = "";
  33. try {
  34. // 创建 Bool 查询构建器
  35. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  36. // 构建查询条件
  37. boolQueryBuilder.must(QueryBuilders.termsQuery("address.keyword", "北京市昌平区", "北京市大兴区", "北京市房山区"))
  38. .filter().add(QueryBuilders.rangeQuery("birthDate").format("yyyy").gte("1990").lte("1995"));
  39. // 构建查询源构建器
  40. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  41. searchSourceBuilder.query(boolQueryBuilder);
  42. // 创建查询请求对象,将查询对象配置到其中
  43. SearchRequest searchRequest = new SearchRequest(queryDto.getIndexName());
  44. searchRequest.source(searchSourceBuilder);
  45. // 执行查询,然后处理响应结果
  46. SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
  47. // 根据状态和数据条数验证是否返回了数据
  48. if (RestStatus.OK.equals(searchResponse.status()) && searchResponse.getHits().getTotalHits().value > 0) {
  49. SearchHits hits = searchResponse.getHits();
  50. for (SearchHit hit : hits) {
  51. // 将 JSON 转换成对象
  52. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  53. // 输出查询信息
  54. log.info(userInfo.toString());
  55. }
  56. }
  57. result = searchResponse.getHits();
  58. } catch (IOException e) {
  59. log.error("", e);
  60. }
  61. return result;
  62. }
  63. }

八、聚合查询操作示例

1、Metric 聚合分析

(1)、Restful 操作示例

  1. GET http://localhost:9200/testindex/_search
  2. 1、统计员工总数、工资最高值、工资最低值、工资平均工资、工资总和:
  3. //请求
  4. {
  5. "size": 0,
  6. "aggs": {
  7. "salary_stats": {
  8. "stats": {
  9. "field": "salary"
  10. }
  11. }
  12. }
  13. }
  14. 2、统计员工工资最低值:
  15. //请求
  16. {
  17. "size": 0,
  18. "aggs": {
  19. "salary_min": {
  20. "min": {
  21. "field": "salary"
  22. }
  23. }
  24. }
  25. }
  26. 3、统计员工工资最高值:
  27. //请求
  28. {
  29. "size": 0,
  30. "aggs": {
  31. "salary_max": {
  32. "max": {
  33. "field": "salary"
  34. }
  35. }
  36. }
  37. }
  38. 4、统计员工工资平均值:
  39. //请求
  40. {
  41. "size": 0,
  42. "aggs": {
  43. "salary_avg": {
  44. "avg": {
  45. "field": "salary"
  46. }
  47. }
  48. }
  49. }
  50. 5、统计员工工资总值:
  51. //请求
  52. {
  53. "size": 0,
  54. "aggs": {
  55. "salary_sum": {
  56. "sum": {
  57. "field": "salary"
  58. }
  59. }
  60. }
  61. }
  62. 6、统计员工总数:
  63. //请求
  64. {
  65. "size": 0,
  66. "aggs": {
  67. "employee_count": {
  68. "value_count": {
  69. "field": "salary"
  70. }
  71. }
  72. }
  73. }
  74. 7、统计员工工资百分位:
  75. //请求
  76. {
  77. "size": 0,
  78. "aggs": {
  79. "salary_percentiles": {
  80. "percentiles": {
  81. "field": "salary"
  82. }
  83. }
  84. }
  85. }

(2)、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.aggregation;
  2. import com.example.elasticsearch.demos.web.model.dto.MatchQueryDto;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.elasticsearch.action.search.SearchRequest;
  5. import org.elasticsearch.action.search.SearchResponse;
  6. import org.elasticsearch.client.RequestOptions;
  7. import org.elasticsearch.client.RestHighLevelClient;
  8. import org.elasticsearch.rest.RestStatus;
  9. import org.elasticsearch.search.aggregations.AggregationBuilder;
  10. import org.elasticsearch.search.aggregations.AggregationBuilders;
  11. import org.elasticsearch.search.aggregations.Aggregations;
  12. import org.elasticsearch.search.aggregations.metrics.*;
  13. import org.elasticsearch.search.builder.SearchSourceBuilder;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.beans.factory.annotation.Value;
  16. import org.springframework.stereotype.Component;
  17. import org.springframework.stereotype.Service;
  18. import java.io.IOException;
  19. /**
  20. * 聚合 Metric
  21. */
  22. @Slf4j
  23. @Service
  24. public class AggrMetricService {
  25. @Autowired
  26. private RestHighLevelClient restHighLevelClient;
  27. @Value("${myindex}")
  28. private String indexName;
  29. /**
  30. * stats 统计员工总数、工资最高值、工资最低值、工资平均工资、工资总和
  31. * @param queryDto
  32. */
  33. public Object aggregationStats(MatchQueryDto queryDto) {
  34. String responseResult = "";
  35. try {
  36. // 设置聚合条件
  37. String field = queryDto.getKey();
  38. AggregationBuilder aggr = AggregationBuilders.stats(field + "_stats").field(field);
  39. // 查询源构建器
  40. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  41. searchSourceBuilder.aggregation(aggr);
  42. // 设置查询结果不返回,只返回聚合结果
  43. searchSourceBuilder.size(0);
  44. // 创建查询请求对象,将查询条件配置到其中
  45. SearchRequest request = new SearchRequest(indexName);
  46. request.source(searchSourceBuilder);
  47. // 执行请求
  48. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  49. // 获取响应中的聚合信息
  50. Aggregations aggregations = response.getAggregations();
  51. // 输出内容
  52. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  53. // 转换为 Stats 对象
  54. ParsedStats aggregation = aggregations.get(field + "_stats");
  55. log.info("-------------------------------------------");
  56. log.info("聚合信息: {}", field);
  57. log.info("count:{}", aggregation.getCount());
  58. log.info("avg:{}", aggregation.getAvg());
  59. log.info("max:{}", aggregation.getMax());
  60. log.info("min:{}", aggregation.getMin());
  61. log.info("sum:{}", aggregation.getSum());
  62. log.info("-------------------------------------------");
  63. }
  64. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  65. responseResult = response.toString();
  66. } catch (IOException e) {
  67. log.error("", e);
  68. }
  69. return responseResult;
  70. }
  71. /**
  72. * min 统计员工工资最低值
  73. */
  74. public Object aggregationMin() {
  75. String responseResult = "";
  76. try {
  77. // 设置聚合条件
  78. AggregationBuilder aggr = AggregationBuilders.min("salary_min").field("salary");
  79. // 查询源构建器
  80. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  81. searchSourceBuilder.aggregation(aggr);
  82. searchSourceBuilder.size(0);
  83. // 创建查询请求对象,将查询条件配置到其中
  84. SearchRequest request = new SearchRequest(indexName);
  85. request.source(searchSourceBuilder);
  86. // 执行请求
  87. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  88. // 获取响应中的聚合信息
  89. Aggregations aggregations = response.getAggregations();
  90. // 输出内容
  91. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  92. // 转换为 Min 对象
  93. ParsedMin aggregation = aggregations.get("salary_min");
  94. log.info("-------------------------------------------");
  95. log.info("聚合信息:");
  96. log.info("min:{}", aggregation.getValue());
  97. log.info("-------------------------------------------");
  98. }
  99. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  100. responseResult = response.toString();
  101. } catch (IOException e) {
  102. log.error("", e);
  103. }
  104. return responseResult;
  105. }
  106. /**
  107. * max 统计员工工资最高值
  108. */
  109. public Object aggregationMax() {
  110. String responseResult = "";
  111. try {
  112. // 设置聚合条件
  113. AggregationBuilder aggr = AggregationBuilders.max("salary_max").field("salary");
  114. // 查询源构建器
  115. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  116. searchSourceBuilder.aggregation(aggr);
  117. searchSourceBuilder.size(0);
  118. // 创建查询请求对象,将查询条件配置到其中
  119. SearchRequest request = new SearchRequest(indexName);
  120. request.source(searchSourceBuilder);
  121. // 执行请求
  122. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  123. // 获取响应中的聚合信息
  124. Aggregations aggregations = response.getAggregations();
  125. // 输出内容
  126. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  127. // 转换为 Max 对象
  128. ParsedMax aggregation = aggregations.get("salary_max");
  129. log.info("-------------------------------------------");
  130. log.info("聚合信息:");
  131. log.info("max:{}", aggregation.getValue());
  132. log.info("-------------------------------------------");
  133. }
  134. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  135. responseResult = response.toString();
  136. } catch (IOException e) {
  137. log.error("", e);
  138. }
  139. return responseResult;
  140. }
  141. /**
  142. * avg 统计员工工资平均值
  143. */
  144. public Object aggregationAvg() {
  145. String responseResult = "";
  146. try {
  147. // 设置聚合条件
  148. AggregationBuilder aggr = AggregationBuilders.avg("salary_avg").field("salary");
  149. // 查询源构建器
  150. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  151. searchSourceBuilder.aggregation(aggr);
  152. searchSourceBuilder.size(0);
  153. // 创建查询请求对象,将查询条件配置到其中
  154. SearchRequest request = new SearchRequest(indexName);
  155. request.source(searchSourceBuilder);
  156. // 执行请求
  157. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  158. // 获取响应中的聚合信息
  159. Aggregations aggregations = response.getAggregations();
  160. // 输出内容
  161. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  162. // 转换为 Avg 对象
  163. ParsedAvg aggregation = aggregations.get("salary_avg");
  164. log.info("-------------------------------------------");
  165. log.info("聚合信息:");
  166. log.info("avg:{}", aggregation.getValue());
  167. log.info("-------------------------------------------");
  168. }
  169. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  170. responseResult = response.toString();
  171. } catch (IOException e) {
  172. log.error("", e);
  173. }
  174. return responseResult;
  175. }
  176. /**
  177. * sum 统计员工工资总值
  178. */
  179. public Object aggregationSum() {
  180. String responseResult = "";
  181. try {
  182. // 设置聚合条件
  183. SumAggregationBuilder aggr = AggregationBuilders.sum("salary_sum").field("salary");
  184. // 查询源构建器
  185. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  186. searchSourceBuilder.aggregation(aggr);
  187. searchSourceBuilder.size(0);
  188. // 创建查询请求对象,将查询条件配置到其中
  189. SearchRequest request = new SearchRequest(indexName);
  190. request.source(searchSourceBuilder);
  191. // 执行请求
  192. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  193. // 获取响应中的聚合信息
  194. Aggregations aggregations = response.getAggregations();
  195. // 输出内容
  196. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  197. // 转换为 Sum 对象
  198. ParsedSum aggregation = aggregations.get("salary_sum");
  199. log.info("-------------------------------------------");
  200. log.info("聚合信息:");
  201. log.info("sum:{}", String.valueOf((aggregation.getValue())));
  202. log.info("-------------------------------------------");
  203. }
  204. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  205. responseResult = response.toString();
  206. } catch (IOException e) {
  207. log.error("", e);
  208. }
  209. return responseResult;
  210. }
  211. /**
  212. * count 统计员工总数
  213. */
  214. public Object aggregationCount() {
  215. String responseResult = "";
  216. try {
  217. // 设置聚合条件
  218. AggregationBuilder aggr = AggregationBuilders.count("employee_count").field("salary");
  219. // 查询源构建器
  220. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  221. searchSourceBuilder.aggregation(aggr);
  222. searchSourceBuilder.size(0);
  223. // 创建查询请求对象,将查询条件配置到其中
  224. SearchRequest request = new SearchRequest(indexName);
  225. request.source(searchSourceBuilder);
  226. // 执行请求
  227. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  228. // 获取响应中的聚合信息
  229. Aggregations aggregations = response.getAggregations();
  230. // 输出内容
  231. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  232. // 转换为 ValueCount 对象
  233. ParsedValueCount aggregation = aggregations.get("employee_count");
  234. log.info("-------------------------------------------");
  235. log.info("聚合信息:");
  236. log.info("count:{}", aggregation.getValue());
  237. log.info("-------------------------------------------");
  238. }
  239. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  240. responseResult = response.toString();
  241. } catch (IOException e) {
  242. log.error("", e);
  243. }
  244. return responseResult;
  245. }
  246. /**
  247. * percentiles 统计员工工资百分位
  248. */
  249. public Object aggregationPercentiles() {
  250. String responseResult = "";
  251. try {
  252. // 设置聚合条件
  253. AggregationBuilder aggr = AggregationBuilders.percentiles("salary_percentiles").field("salary");
  254. // 查询源构建器
  255. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  256. searchSourceBuilder.aggregation(aggr);
  257. searchSourceBuilder.size(0);
  258. // 创建查询请求对象,将查询条件配置到其中
  259. SearchRequest request = new SearchRequest(indexName);
  260. request.source(searchSourceBuilder);
  261. // 执行请求
  262. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  263. // 获取响应中的聚合信息
  264. Aggregations aggregations = response.getAggregations();
  265. // 输出内容
  266. if (RestStatus.OK.equals(response.status()) || aggregations != null) {
  267. // 转换为 Percentiles 对象
  268. ParsedPercentiles aggregation = aggregations.get("salary_percentiles");
  269. log.info("-------------------------------------------");
  270. log.info("聚合信息:");
  271. for (Percentile percentile : aggregation) {
  272. log.info("百分位:{}:{}", percentile.getPercent(), percentile.getValue());
  273. }
  274. log.info("-------------------------------------------");
  275. }
  276. // 根据具体业务逻辑返回不同结果,这里为了方便直接将返回响应对象Json串
  277. responseResult = response.toString();
  278. } catch (IOException e) {
  279. log.error("", e);
  280. }
  281. return responseResult;
  282. }
  283. }

2、Bucket 聚合分析

(1)、Restful 操作示例

  1. GET http://localhost:9200/testindex/_search
  2. 1、按岁数进行聚合分桶,统计各个岁数员工的人数:
  3. //请求
  4. {
  5. "size": 0,
  6. "aggs": {
  7. "age_bucket": {
  8. "terms": {
  9. "field": "age",
  10. "size": "10"
  11. }
  12. }
  13. }
  14. }
  15. 2、按工资范围进行聚合分桶,统计工资在 3000-5000、5000-9000 和 9000 以上的员工信息:
  16. //请求
  17. {
  18. "aggs": {
  19. "salary_range_bucket": {
  20. "range": {
  21. "field": "salary",
  22. "ranges": [
  23. {
  24. "key": "低级员工",
  25. "to": 3000
  26. },{
  27. "key": "中级员工",
  28. "from": 5000,
  29. "to": 9000
  30. },{
  31. "key": "高级员工",
  32. "from": 9000
  33. }
  34. ]
  35. }
  36. }
  37. }
  38. }
  39. 3、按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
  40. //请求
  41. {
  42. "size": 10,
  43. "aggs": {
  44. "date_range_bucket": {
  45. "date_range": {
  46. "field": "birthDate",
  47. "format": "yyyy",
  48. "ranges": [
  49. {
  50. "key": "出生日期1985-1990的员工",
  51. "from": "1985",
  52. "to": "1990"
  53. },{
  54. "key": "出生日期1990-1995的员工",
  55. "from": "1990",
  56. "to": "1995"
  57. }
  58. ]
  59. }
  60. }
  61. }
  62. }
  63. 4、按工资多少进行聚合分桶,设置统计的最小值为 0,最大值为 12000,区段间隔为 3000:
  64. //请求
  65. {
  66. "size": 0,
  67. "aggs": {
  68. "salary_histogram": {
  69. "histogram": {
  70. "field": "salary",
  71. "extended_bounds": {
  72. "min": 0,
  73. "max": 12000
  74. },
  75. "interval": 3000
  76. }
  77. }
  78. }
  79. }
  80. 5、按出生日期进行分桶:
  81. //请求
  82. {
  83. "size": 0,
  84. "aggs": {
  85. "birthday_histogram": {
  86. "date_histogram": {
  87. "format": "yyyy",
  88. "field": "birthDate",
  89. "interval": "year"
  90. }
  91. }
  92. }
  93. }

(2)、Java 代码示例 

  1. package com.example.elasticsearch.demos.web.service.aggregation;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.elasticsearch.action.search.SearchRequest;
  4. import org.elasticsearch.action.search.SearchResponse;
  5. import org.elasticsearch.client.RequestOptions;
  6. import org.elasticsearch.client.RestHighLevelClient;
  7. import org.elasticsearch.rest.RestStatus;
  8. import org.elasticsearch.search.aggregations.AggregationBuilder;
  9. import org.elasticsearch.search.aggregations.AggregationBuilders;
  10. import org.elasticsearch.search.aggregations.Aggregations;
  11. import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
  12. import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
  13. import org.elasticsearch.search.aggregations.bucket.range.Range;
  14. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  15. import org.elasticsearch.search.builder.SearchSourceBuilder;
  16. import org.springframework.beans.factory.annotation.Autowired;
  17. import org.springframework.beans.factory.annotation.Value;
  18. import org.springframework.stereotype.Service;
  19. import java.io.IOException;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. /**
  24. * 聚合 Bucket
  25. */
  26. @Slf4j
  27. @Service
  28. public class AggrBucketService {
  29. @Autowired
  30. private RestHighLevelClient restHighLevelClient;
  31. @Value("${myindex}")
  32. private String indexName;
  33. /**
  34. * 按岁数进行聚合分桶,统计各个岁数员工的人数:
  35. */
  36. public Object aggrBucketTerms() {
  37. Map<String, Long> keyCountMap = new HashMap<>();
  38. try {
  39. AggregationBuilder aggr = AggregationBuilders.terms("age_bucket").field("age");
  40. // 查询源构建器
  41. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  42. searchSourceBuilder.size(10);
  43. searchSourceBuilder.aggregation(aggr);
  44. // 创建查询请求对象,将查询条件配置到其中
  45. SearchRequest request = new SearchRequest(indexName);
  46. request.source(searchSourceBuilder);
  47. // 执行请求
  48. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  49. // 获取响应中的聚合信息
  50. Aggregations aggregations = response.getAggregations();
  51. // 输出内容
  52. if (RestStatus.OK.equals(response.status())) {
  53. // 分桶
  54. Terms byCompanyAggregation = aggregations.get("age_bucket");
  55. List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets();
  56. // 输出各个桶的内容
  57. log.info("-------------------------------------------");
  58. log.info("聚合信息:");
  59. for (Terms.Bucket bucket : buckets) {
  60. keyCountMap.put(bucket.getKeyAsString(), bucket.getDocCount());
  61. log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
  62. }
  63. log.info("-------------------------------------------");
  64. }
  65. } catch (IOException e) {
  66. log.error("", e);
  67. }
  68. return keyCountMap;
  69. }
  70. /**
  71. * 按工资范围进行聚合分桶,统计工资在 3000-5000、5000-9000 和 9000 以上的员工信息:
  72. */
  73. public Object aggrBucketRange() {
  74. Map<String, Long> keyCountMap = new HashMap<>();
  75. try {
  76. AggregationBuilder aggr = AggregationBuilders.range("salary_range_bucket")
  77. .field("salary")
  78. .addUnboundedTo("低级员工", 3000)
  79. .addRange("中级员工", 5000, 9000)
  80. .addUnboundedFrom("高级员工", 9000);
  81. // 查询源构建器
  82. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  83. searchSourceBuilder.size(0);
  84. searchSourceBuilder.aggregation(aggr);
  85. // 创建查询请求对象,将查询条件配置到其中
  86. SearchRequest request = new SearchRequest(indexName);
  87. request.source(searchSourceBuilder);
  88. // 执行请求
  89. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  90. // 获取响应中的聚合信息
  91. Aggregations aggregations = response.getAggregations();
  92. // 输出内容
  93. if (RestStatus.OK.equals(response.status())) {
  94. // 分桶
  95. Range byCompanyAggregation = aggregations.get("salary_range_bucket");
  96. List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
  97. // 输出各个桶的内容
  98. log.info("-------------------------------------------");
  99. log.info("聚合信息:");
  100. for (Range.Bucket bucket : buckets) {
  101. keyCountMap.put(bucket.getKeyAsString(), bucket.getDocCount());
  102. log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
  103. }
  104. log.info("-------------------------------------------");
  105. }
  106. } catch (IOException e) {
  107. log.error("", e);
  108. }
  109. return keyCountMap;
  110. }
  111. /**
  112. * 按照时间范围进行分桶,统计 1985-1990 年和 1990-1995 年出生的员工信息:
  113. */
  114. public Object aggrBucketDateRange() {
  115. Map<String, Long> keyCountMap = new HashMap<>();
  116. try {
  117. AggregationBuilder aggr = AggregationBuilders.dateRange("date_range_bucket")
  118. .field("birthDate")
  119. .format("yyyy")
  120. .addRange("出生日期1985-1990的员工", "1985", "1990")
  121. .addRange("出生日期1990-1995的员工", "1990", "1995");
  122. // 查询源构建器
  123. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  124. searchSourceBuilder.size(0);
  125. searchSourceBuilder.aggregation(aggr);
  126. // 创建查询请求对象,将查询条件配置到其中
  127. SearchRequest request = new SearchRequest(indexName);
  128. request.source(searchSourceBuilder);
  129. // 执行请求
  130. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  131. // 获取响应中的聚合信息
  132. Aggregations aggregations = response.getAggregations();
  133. // 输出内容
  134. if (RestStatus.OK.equals(response.status())) {
  135. // 分桶
  136. Range byCompanyAggregation = aggregations.get("date_range_bucket");
  137. List<? extends Range.Bucket> buckets = byCompanyAggregation.getBuckets();
  138. // 输出各个桶的内容
  139. log.info("-------------------------------------------");
  140. log.info("聚合信息:");
  141. for (Range.Bucket bucket : buckets) {
  142. keyCountMap.put(bucket.getKeyAsString(), bucket.getDocCount());
  143. log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
  144. }
  145. log.info("-------------------------------------------");
  146. }
  147. } catch (IOException e) {
  148. log.error("", e);
  149. }
  150. return keyCountMap;
  151. }
  152. /**
  153. * 按工资多少进行聚合分桶
  154. */
  155. public Object aggrBucketHistogram() {
  156. Map<String, Long> keyCountMap = new HashMap<>();
  157. try {
  158. //按工资多少进行聚合分桶,设置统计的最小值为 0,最大值为 12000,区段间隔为 3000:
  159. AggregationBuilder aggr = AggregationBuilders.histogram("salary_histogram")
  160. .field("salary")
  161. .extendedBounds(0, 12000)
  162. .interval(3000);
  163. // 查询源构建器
  164. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  165. searchSourceBuilder.size(0);
  166. searchSourceBuilder.aggregation(aggr);
  167. // 创建查询请求对象,将查询条件配置到其中
  168. SearchRequest request = new SearchRequest(indexName);
  169. request.source(searchSourceBuilder);
  170. // 执行请求
  171. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  172. // 获取响应中的聚合信息
  173. Aggregations aggregations = response.getAggregations();
  174. // 输出内容
  175. if (RestStatus.OK.equals(response.status())) {
  176. // 分桶
  177. Histogram byCompanyAggregation = aggregations.get("salary_histogram");
  178. List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
  179. // 输出各个桶的内容
  180. log.info("-------------------------------------------");
  181. log.info("聚合信息:");
  182. for (Histogram.Bucket bucket : buckets) {
  183. keyCountMap.put(bucket.getKeyAsString(), bucket.getDocCount());
  184. log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
  185. }
  186. log.info("-------------------------------------------");
  187. }
  188. } catch (IOException e) {
  189. log.error("", e);
  190. }
  191. return keyCountMap;
  192. }
  193. /**
  194. * 按出生日期进行分桶:
  195. */
  196. public Object aggrBucketDateHistogram() {
  197. Map<String, Long> keyCountMap = new HashMap<>();
  198. try {
  199. AggregationBuilder aggr = AggregationBuilders.dateHistogram("birthday_histogram")
  200. .field("birthDate")
  201. .interval(1)
  202. .dateHistogramInterval(DateHistogramInterval.YEAR)
  203. .format("yyyy");
  204. // 查询源构建器
  205. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  206. searchSourceBuilder.size(0);
  207. searchSourceBuilder.aggregation(aggr);
  208. // 创建查询请求对象,将查询条件配置到其中
  209. SearchRequest request = new SearchRequest(indexName);
  210. request.source(searchSourceBuilder);
  211. // 执行请求
  212. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  213. // 获取响应中的聚合信息
  214. Aggregations aggregations = response.getAggregations();
  215. // 输出内容
  216. if (RestStatus.OK.equals(response.status())) {
  217. // 分桶
  218. Histogram byCompanyAggregation = aggregations.get("birthday_histogram");
  219. List<? extends Histogram.Bucket> buckets = byCompanyAggregation.getBuckets();
  220. // 输出各个桶的内容
  221. log.info("-------------------------------------------");
  222. log.info("聚合信息:");
  223. for (Histogram.Bucket bucket : buckets) {
  224. keyCountMap.put(bucket.getKeyAsString(), bucket.getDocCount());
  225. log.info("桶名:{} | 总数:{}", bucket.getKeyAsString(), bucket.getDocCount());
  226. }
  227. log.info("-------------------------------------------");
  228. }
  229. } catch (IOException e) {
  230. log.error("", e);
  231. }
  232. return keyCountMap;
  233. }
  234. }

3、Metric 与 Bucket 聚合分析

(1)、Restful 操作示例

按照员工岁数分桶、然后统计每个岁数员工工资最高值:

  1. GET http://localhost:9200/testindex/_search
  2. //请求
  3. {
  4. "size": 0,
  5. "aggs": {
  6. "salary_bucket": {
  7. "terms": {
  8. "field": "age",
  9. "size": "10"
  10. },
  11. "aggs": {
  12. "salary_max_user": {
  13. "top_hits": {
  14. "size": 1,
  15. "sort": [
  16. {
  17. "salary": {
  18. "order": "desc"
  19. }
  20. }
  21. ]
  22. }
  23. }
  24. }
  25. }
  26. }
  27. }

(2)、Java 代码示例

  1. package com.example.elasticsearch.demos.web.service.aggregation;
  2. import com.alibaba.fastjson.JSON;
  3. import com.example.elasticsearch.demos.web.model.entity.UserInfo;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.elasticsearch.action.search.SearchRequest;
  6. import org.elasticsearch.action.search.SearchResponse;
  7. import org.elasticsearch.client.RequestOptions;
  8. import org.elasticsearch.client.RestHighLevelClient;
  9. import org.elasticsearch.rest.RestStatus;
  10. import org.elasticsearch.search.SearchHit;
  11. import org.elasticsearch.search.aggregations.AggregationBuilder;
  12. import org.elasticsearch.search.aggregations.AggregationBuilders;
  13. import org.elasticsearch.search.aggregations.Aggregations;
  14. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  15. import org.elasticsearch.search.aggregations.metrics.ParsedTopHits;
  16. import org.elasticsearch.search.builder.SearchSourceBuilder;
  17. import org.elasticsearch.search.sort.SortOrder;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.beans.factory.annotation.Value;
  20. import org.springframework.stereotype.Service;
  21. import java.io.IOException;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. /**
  26. * 聚合 Bucket 与 Metric
  27. */
  28. @Slf4j
  29. @Service
  30. public class AggrBucketMetricService {
  31. @Autowired
  32. private RestHighLevelClient restHighLevelClient;
  33. @Value("${myindex}")
  34. private String indexName;
  35. /**
  36. * topHits 按照员工岁数分桶、然后统计每个岁数员工工资最高值
  37. */
  38. public Object aggregationTopHits() {
  39. Map<String, Float> ageMaxSalaryMap = new HashMap<>();
  40. try {
  41. AggregationBuilder testTop = AggregationBuilders.topHits("salary_max_user")
  42. .size(1)
  43. .sort("salary", SortOrder.DESC);
  44. AggregationBuilder salaryBucket = AggregationBuilders.terms("salary_bucket")
  45. .field("age")
  46. .size(10);
  47. salaryBucket.subAggregation(testTop);
  48. // 查询源构建器
  49. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  50. searchSourceBuilder.size(0);
  51. searchSourceBuilder.aggregation(salaryBucket);
  52. // 创建查询请求对象,将查询条件配置到其中
  53. SearchRequest request = new SearchRequest(indexName);
  54. request.source(searchSourceBuilder);
  55. // 执行请求
  56. SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
  57. // 获取响应中的聚合信息
  58. Aggregations aggregations = response.getAggregations();
  59. // 输出内容
  60. if (RestStatus.OK.equals(response.status())) {
  61. // 分桶
  62. Terms byCompanyAggregation = aggregations.get("salary_bucket");
  63. List<? extends Terms.Bucket> buckets = byCompanyAggregation.getBuckets();
  64. // 输出各个桶的内容
  65. log.info("-------------------------------------------");
  66. log.info("聚合信息:");
  67. for (Terms.Bucket bucket : buckets) {
  68. log.info("桶名:{}", bucket.getKeyAsString());
  69. ParsedTopHits topHits = bucket.getAggregations().get("salary_max_user");
  70. for (SearchHit hit : topHits.getHits()) {
  71. UserInfo userInfo = JSON.parseObject(hit.getSourceAsString(), UserInfo.class);
  72. ageMaxSalaryMap.put(bucket.getKeyAsString(), userInfo.getSalary());
  73. log.info(hit.getSourceAsString());
  74. }
  75. }
  76. log.info("-------------------------------------------");
  77. }
  78. } catch (IOException e) {
  79. log.error("", e);
  80. }
  81. return ageMaxSalaryMap;
  82. }
  83. }

九、项目源码及对应ES安装包

1、elasticsearch-7.6.1安装包

elasticsearch7.6.1icon-default.png?t=N7T8https://download.csdn.net/download/asd051377305/88397087

2、项目源代码

基于SpringBoot+elasticsearch的操作项目icon-default.png?t=N7T8https://download.csdn.net/download/asd051377305/88397090

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/684388
推荐阅读
相关标签
  

闽ICP备14008679号