Spring Boot - 数据库集成06 - 集成ElasticSearch

宁睿  金牌会员 | 2025-2-20 13:51:31 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 942|帖子 942|积分 2836

Spring boot 集成 ElasticSearch


  

Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用 JSON作为文档序列化的格式
  1. {
  2.     "name" :     "John",
  3.     "sex" :      "Male",
  4.     "age" :      25,
  5.     "birthDate": "1990/05/01",
  6.     "about" :    "I love to go rock climbing",
  7.     "interests": [ "sports", "music" ]
  8. }
复制代码
ES和传统的关系型数据库相关术语对比如下:
关系型数据库ES数据库(Database)索引(index)表(table)类型(Type)[es6.0.0废弃]行(row)文档(document)列(column)字段(field)表布局(schema)映射(mapping)索引反向索引SQL查询DSLSelect * from tableGet http://…update table set…Put http://…deleteDelete http://… 一:前置工作

1:项目搭建和依赖导入

   

  • spring data -> spring-boot-starter-data-elasticsearch -> repo & template
  • transport & elasticsearch-rest-high-level-client -> client
  1. <dependencies>
  2.     <!-- spring boot启动器 -->
  3.     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
  4.     <dependency>
  5.         <groupId>org.springframework.boot</groupId>
  6.         <artifactId>spring-boot-starter</artifactId>
  7.     </dependency>
  8.     <!-- spring boot web启动器 -->
  9.     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
  10.     <dependency>
  11.         <groupId>org.springframework.boot</groupId>
  12.         <artifactId>spring-boot-starter-web</artifactId>
  13.     </dependency>
  14.     <!-- spring boot 测试启动器 -->
  15.     <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
  16.     <dependency>
  17.         <groupId>org.springframework.boot</groupId>
  18.         <artifactId>spring-boot-starter-test</artifactId>
  19.         <scope>test</scope>
  20.     </dependency>
  21.     <!-- lombok -->
  22.     <dependency>
  23.         <groupId>org.projectlombok</groupId>
  24.         <artifactId>lombok</artifactId>
  25.     </dependency>
  26.     <!-- commons-lang3工具类 -->
  27.     <dependency>
  28.         <groupId>org.apache.commons</groupId>
  29.         <artifactId>commons-lang3</artifactId>
  30.     </dependency>
  31.     <!-- spring data elasticsearch -->
  32.     <dependency>
  33.         <groupId>org.springframework.boot</groupId>
  34.         <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  35.     </dependency>
  36.     <!-- es 高阶客户端 -->
  37.     <dependency>
  38.         <groupId>org.elasticsearch.client</groupId>
  39.         <artifactId>elasticsearch-rest-high-level-client</artifactId>
  40.     </dependency>
  41.     <!-- es 低阶客户端 -->
  42.     <dependency>
  43.         <groupId>org.elasticsearch.client</groupId>
  44.         <artifactId>transport</artifactId>
  45.     </dependency>
  46. </dependencies>
复制代码
  1. spring:
  2.   elasticsearch:
  3.     rest:
  4.       uris: http://localhost:9200,http://localhost:9201,http://localhost:9202 # 集群地址
  5.       connection-timeout: 5s # 连接超时时间
  6.       read-timeout: 30s # 读取超时时间
  7.       max-connections: 100 # 最大连接数
  8.       max-connections-per-route: 20 # 每个路由的最大连接数
  9.       password: es_password
复制代码
2:客户端连接相关构建

  1. package com.cui.es_demo.config;
  2. import org.elasticsearch.client.RestHighLevelClient;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.data.elasticsearch.client.ClientConfiguration;
  7. import org.springframework.data.elasticsearch.client.RestClients;
  8. import java.util.Arrays;
  9. /**
  10. * @author cui haida
  11. * 2025/1/29
  12. */
  13. @Configuration
  14. @ConfigurationProperties(prefix = "spring.elasticsearch.rest")
  15. public class ElasticSearchClientConfig {
  16.     /**
  17.      * 集群地址
  18.      */
  19.     private String uris;
  20.     /**
  21.      * 用户名和密码
  22.      */
  23.     private String username;
  24.     private String password;
  25.     @Bean
  26.     public RestHighLevelClient restHighLevelClient() {
  27.         // 创建连接, 指定url和用户名密码
  28.         ClientConfiguration build = ClientConfiguration.builder()
  29.                 .connectedTo(uris)
  30.                 .withBasicAuth(username, password)
  31.                 .build();
  32.         return RestClients.create(build).rest();
  33.     }
  34. }
复制代码
3:实体类相关注解设置说明

  1. package com.cui.es_demo.entity;
  2. import com.fasterxml.jackson.annotation.JsonFormat;
  3. import lombok.AllArgsConstructor;
  4. import lombok.Data;
  5. import lombok.NoArgsConstructor;
  6. import org.springframework.data.annotation.Id;
  7. import org.springframework.data.elasticsearch.annotations.DateFormat;
  8. import org.springframework.data.elasticsearch.annotations.Document;
  9. import org.springframework.data.elasticsearch.annotations.Field;
  10. import org.springframework.data.elasticsearch.annotations.FieldType;
  11. import java.time.LocalDateTime;
  12. /**
  13. * 实体类相关注解介绍
  14. * - @Document:指定索引库的名称,以及分片数、副本数、刷新间隔、是否允许创建索引
  15. * - @Id:指定主键
  16. * - @Field:指定字段的名称,以及字段的分词器,以及字段的存储类型,以及字段的分词器
  17. * - @JsonFormat:指定日期格式(注意日期类型字段不要用java.util.Date类型,要用java.time.LocalDate或java.time.LocalDateTime类型)
  18. * @author cui haida
  19. * 2025/1/30
  20. */
  21. @NoArgsConstructor
  22. @AllArgsConstructor
  23. @Data
  24. // 指定对应的索引名称,, 主分片数 = 3, 副本数 = 1, 刷新间隔 = 1s, 允许创建索引
  25. @Document(indexName = "person", shards = 3, replicas = 1, refreshInterval = "1s", createIndex = true)
  26. public class Person {
  27.     @Id
  28.     private String id;
  29.     @Field(type = FieldType.Keyword)
  30.     private String name;
  31.     @Field(type = FieldType.Keyword)
  32.     private String age;
  33.     // text类型,并使用IK最粗粒度的分词器
  34.     // 检索时的分词器采用的是最细粒度的IK分词器
  35.     @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
  36.     private String address;
  37.     // 注意日期格式的特殊处理
  38.     // 指定格式化和时区
  39.     @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
  40.     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  41.     private LocalDateTime createTime;
  42. }
复制代码
二:客户端client相关操作说明

   对索引相关的操作都不推荐在这里执行,建议直接使用Kibana
  文档的更新和插入操作建议使用repo & template方式
  这里只先容查询相关操作
  1:检索流程

1.1:构建请求request和源source

  1. // 声明查询请求对象
  2. SearchRequest client = new SearchRequest();
  3. // 指定使用的索引(数据库)
  4. client.indices(index);
  5. // 声明searchSourceBuilder源对象
  6. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  7. // ======================== 源对象其他常见设置 ===========================
  8. // 1. 排序和浅显分页
  9. // from  & size 默认都是-1,也就是有多少显示多少
  10. // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
  11. searchSourceBuilder.from(0);
  12. searchSourceBuilder.size(9999);
  13. // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
  14. searchSourceBuilder
  15.     .sort("age", SortOrder.DESC)
  16.     .sort("name", SortOrder.ASC);
复制代码
1.2:查询建造器queryBuilder构建

  1. // =============== 以boolean为例 ==================
  2. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
  3.     .must(QueryBuilders.termQuery("name.keyword", "王五"))
  4.     .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
  5.     // 可以进行bool嵌套
  6.     .must(QueryBuilders.boolQuery()
  7.           .must(QueryBuilders.matchQuery("name", "张三"))
  8.          )
  9.     .boost(2.0f);
  10. // 加入对应的builder到searchSourceBuilder中
  11. searchSourceBuilder.query(boolQueryBuilder);
  12. // ============ 如果有高亮设置 ===============
  13. HighlightBuilder highlightBuilder = new HighlightBuilder();
  14. highlightBuilder.preTags("<font style='color: red'>");
  15. highlightBuilder.postTags("</font>");
  16. highlightBuilder.field("name").field("age");
  17. //同一字段中存在多个高亮值 设置都高亮
  18. highlightBuilder.requireFieldMatch(true);
  19. // 高亮属性中加入高亮配置器
  20. searchSourceBuilder.highlighter(highlightBuilder);
复制代码
1.3:请求建造器参加到请求源,请求源参加到请求中

  1. searchSourceBuilder.query(xxxQueryBuilder);
  2. // 封装request,指定要request的索引index,指定source -> searchSourceBuilder
  3. SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);
  4. // 通过client.search获取响应
  5. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
复制代码
1.4:效果hits和highlight分析

  1. List<Map<String, Object>> list = new ArrayList<>();
  2. SearchResponse searchResponse = this.client.search(client, RequestOptions.DEFAULT);
  3. if (searchResponse == null) {
  4.     log.warn("没有返回值");
  5.     return;
  6. }
  7. // todo: 然后从这个searchResponse解析各种的值 -> 根据返回结构
  8. int failedShards = searchResponse.getFailedShards();
  9. System.out.println("失败的分片数:" + failedShards);
  10. int successfulShards = searchResponse.getSuccessfulShards();
  11. System.out.println("成功的分片数:" + successfulShards);
  12. RestStatus status = searchResponse.status();
  13. System.out.println("状态:" + status);
  14. //解析高亮数据
  15. SearchHits hits = searchResponse.getHits();
  16. System.out.println(hits.getMaxScore());
  17. for (SearchHit hit : hits) {
  18.     System.out.println("fields: " + hit.getFields());
  19.     System.out.println("index is:" + hit.getIndex());
  20.     System.out.println("document field is: " + hit.getDocumentFields());
  21.     System.out.println("metadata field is: " + hit.getMetadataFields());
  22.     System.out.println("score is: " + hit.getScore());
  23.     System.out.println("-----------------");
  24.     //原始数据,不包含高亮的数据
  25.     Map<String, Object> sourceMap = hit.getSourceAsMap();
  26.     //高亮数据,拿到高亮字段name
  27.     Map<String, HighlightField> highlightFields = hit.getHighlightFields();
  28.     HighlightField highlightTitle = highlightFields.get("name");
  29.     //将原始数据的name替换
  30.     if (highlightTitle != null) {
  31.         Text[] fragments = highlightTitle.getFragments();
  32.         if (fragments != null && fragments.length > 0) {
  33.             sourceMap.replace("name", fragments[0].toString());
  34.         }
  35.     }
  36.     list.add(sourceMap);//循环将数据添加入列表
  37. }
  38. list.forEach(System.out::println);
复制代码
  1. package com.cui.es_demo.client;
  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.common.text.Text;
  8. import org.elasticsearch.index.query.BoolQueryBuilder;
  9. import org.elasticsearch.index.query.QueryBuilders;
  10. import org.elasticsearch.rest.RestStatus;
  11. import org.elasticsearch.search.SearchHit;
  12. import org.elasticsearch.search.SearchHits;
  13. import org.elasticsearch.search.builder.SearchSourceBuilder;
  14. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  15. import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
  16. import org.elasticsearch.search.sort.SortOrder;
  17. import org.springframework.stereotype.Service;
  18. import javax.annotation.Resource;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. import java.util.Map;
  22. /**
  23. * 客户端相关操作
  24. *
  25. * @author cui haida
  26. * 2025/1/30
  27. */
  28. @Service
  29. @Slf4j
  30. public class ClientDemo {
  31.     //
  32.     private final RestHighLevelClient client;
  33.     public ClientDemo(RestHighLevelClient restHighLevelClient) {
  34.         this.client = restHighLevelClient;
  35.     }
  36.     /**
  37.      * 主要介绍客户端查询操作
  38.      */
  39.     public void searchTest(String[] args) {
  40.         // 声明查询请求,同时声明作用的索引(数据库)
  41.         SearchRequest request = new SearchRequest();
  42.         request.indices("person");
  43.         // 声明searchSourceBuilder源对象
  44.         SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  45.         // ======================== 源对象其他常见设置 ===========================
  46.         // 1. 排序和浅显分页
  47.         // from  & size 默认都是-1,也就是有多少显示多少
  48.         // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
  49.         searchSourceBuilder.from(0);
  50.         searchSourceBuilder.size(9999);
  51.         // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
  52.         searchSourceBuilder
  53.                 .sort("age", SortOrder.DESC)
  54.                 .sort("name", SortOrder.ASC);
  55.         // 构造查询建造器,根据不同的业务需求,进行不同的查询
  56.         // =============== 以boolean为例 ==================
  57.         BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
  58.                 .must(QueryBuilders.termQuery("name.keyword", "王五"))
  59.                 .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
  60.                 // 可以进行bool嵌套
  61.                 .must(QueryBuilders.boolQuery()
  62.                         .must(QueryBuilders.matchQuery("name", "张三"))
  63.                 )
  64.                 .boost(2.0f);
  65.         // 请求构造器加入源中
  66.         searchSourceBuilder.query(boolQueryBuilder);
  67.         // ============ 如果有高亮设置 ===============
  68.         HighlightBuilder highlightBuilder = new HighlightBuilder();
  69.         highlightBuilder.preTags("<font style='color: red'>");
  70.         highlightBuilder.postTags("</font>");
  71.         highlightBuilder.field("name").field("age");
  72.         //同一字段中存在多个高亮值 设置都高亮
  73.         highlightBuilder.requireFieldMatch(true);
  74.         // 高亮属性中加入源中
  75.         searchSourceBuilder.highlighter(highlightBuilder);
  76.         // 进行查询
  77.         // 1. 设置查询源(source -> request)
  78.         request.source(searchSourceBuilder);
  79.         // 2. 通过search方法进行查询
  80.         List<Map<String, Object>> list = new ArrayList<>();
  81.         try {
  82.             SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  83.             // 结果处理
  84.             System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());
  85.             // 然后从这个searchResponse解析各种的值 -> 根据返回结构
  86.             int failedShards = response.getFailedShards();
  87.             System.out.println("失败的分片数:" + failedShards);
  88.             int successfulShards = response.getSuccessfulShards();
  89.             System.out.println("成功的分片数:" + successfulShards);
  90.             RestStatus status = response.status();
  91.             System.out.println("状态:" + status);
  92.             // 解析高亮数据
  93.             SearchHits hits = response.getHits();
  94.             System.out.println(hits.getMaxScore());
  95.             for (SearchHit hit : hits) {
  96.                 System.out.println("fields: " + hit.getFields());
  97.                 System.out.println("index is:" + hit.getIndex());
  98.                 System.out.println("document field is: " + hit.getDocumentFields());
  99.                 System.out.println("metadata field is: " + hit.getMetadataFields());
  100.                 System.out.println("score is: " + hit.getScore());
  101.                 System.out.println("-----------------");
  102.                 // 原始数据,不包含高亮的数据
  103.                 Map<String, Object> sourceMap = hit.getSourceAsMap();
  104.                 // 高亮数据,拿到高亮字段name
  105.                 Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
  106.                 HighlightField highlightTitle = highlightFields.get("name");
  107.                 // 将原始数据的name替换
  108.                 if (highlightTitle != null) {
  109.                     Text[] fragments = highlightTitle.getFragments();
  110.                     if (fragments != null && fragments.length > 0) {
  111.                         sourceMap.replace("name", fragments[0].toString());
  112.                     }
  113.                 }
  114.                 // 循环将数据添加入列表
  115.                 list.add(sourceMap);
  116.             }
  117.             list.forEach(System.out::println);
  118.         } catch (Exception e) {
  119.             e.printStackTrace();
  120.         }
  121.     }
  122. }
复制代码
2:各种查询构造器说明

2.1:MatchQueryBuilder

  1. MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "李四")
  2.     // 李和四必须都出现在name字段中才可以, operator可以是OR
  3.     .operator(Operator.AND)
  4.     // 匹配度必须是 >= 75%才行
  5.     .minimumShouldMatch("75%")
  6.     // 是否忽略数据类型转换异常
  7.     .lenient(true);
复制代码
2.2:RegexQueryBuilder

  1. // 张姓开头的doc
  2. RegexpQueryBuilder regexpQueryBuilder =
  3.     QueryBuilders.regexpQuery("name", "张*").caseInsensitive(true);
复制代码
2.3:IdsQueryBuilder

  1. String[] ids = new String[]{"1", "2", "3"};
  2. IdsQueryBuilder idsQueryBuilder =
  3.     QueryBuilders.idsQuery().addIds(ids);
复制代码
2.4:MatchPhraseQueryBuilder

  1. MatchPhraseQueryBuilder match =
  2.     QueryBuilders.matchPhraseQuery("name", "李四").slop(2);
复制代码
2.5:MatchPhrasePrefixQueryBuilder

  1. MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder =
  2.     QueryBuilders.matchPhrasePrefixQuery("name", "张").maxExpansions(1);
复制代码
2.6:MultiMatchQueryBuilder

  1. String[] fieldNames = new String[]{"name", "age"};
  2. String searchText = "老坛";
  3. MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(searchText, fieldNames)
  4.     // 最多数量匹配
  5.     .type(MultiMatchQueryBuilder.Type.MOST_FIELDS)
  6.     // 使用and操作符和minimum_should_match参数来减少相关度低的文档数量
  7.     .operator(Operator.AND);
复制代码
2.7:TermQueryBuilder

  1. TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name.keyword", "李四");
  2. TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name.keyword", "李四光", "李四");
复制代码
2.8:FuzzyQueryBuilder

  1. FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "李四");
复制代码
2.9:RangeQueryBuilder

  1. RangeQueryBuilder ageRangeFilter = QueryBuilders.rangeQuery("age")
  2.     // greater than 12
  3.     .gt(12)
  4.     // less than 17
  5.     .lt(17);
  6. // 默认是true包含头尾,设置false去掉头尾
  7. RangeQueryBuilder ageRangeFilter2 = QueryBuilders.rangeQuery("age")
  8.     .from(12)
  9.     .to(17)
  10.     // 不包含最后一个元素
  11.     .includeLower(false)
  12.     // 包含第一个元素
  13.     .includeUpper(true);
复制代码
2.10:WildcardQueryBuilder

  1. // wildcard 通配符查询, 支持*,匹配任何字符序列, 包括空,避免*
  2. String queryString = "Lc*dd";
  3. WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("name", queryString);
复制代码
2.11:BoolQueryBuilder

  1. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
  2.     .must(QueryBuilders.termQuery("name.keyword", "王五"))
  3.     .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
  4.     // 可以进行bool嵌套
  5.     .must(QueryBuilders.boolQuery()
  6.           .must(QueryBuilders.matchQuery("name", "张三"))
  7.          )
  8.     .boost(2.0f);
  9. searchSourceBuilder.query(boolQueryBuilder);
复制代码
2.12:其他的QueryBuilder

  1. // 16: 其他查询
  2. // moreLikeThisQuery: 实现基于内容推荐, 支持实现一句话相似文章查询
  3. // percent_terms_to_match:匹配项(term)的百分比,默认是0.3
  4. // min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
  5. // max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
  6. // stop_words:设置停止词,匹配时会忽略停止词
  7. // min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
  8. // max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
  9. // min_word_len:最小的词语长度,默认是0
  10. // max_word_len:最多的词语长度,默认无限制
  11. // boost_terms:设置词语权重,默认是1
  12. // boost:设置查询权重,默认是1
  13. // analyzer:设置使用的分词器,默认是使用该字段指定的分词器
  14. QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery(new String[]{"王"})
  15.     // 一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
  16.     .minTermFreq(1)
  17.     // 一条查询语句中允许最多查询词语的个数,默认是25
  18.     .maxQueryTerms(3);
  19. // 查询条件
  20. searchSourceBuilder.query(matchQueryBuilder)
  21.     // 后置过滤器,id, exists, term, range
  22.     .postFilter(QueryBuilders.existsQuery("tag"));
复制代码
3:聚合建造器说明

3.1:构造聚合条件

  1. TermsAggregationBuilder aggregation =
  2.     AggregationBuilders.terms(等于的值).field(字段名称) // 进行分桶操作
  3.         .subAggregation(AggregationBuilders.avg(桶的名字,随便起的).field(根据xx字段分桶)) // 对每一个分桶,进行子聚合
复制代码
3.2:将聚合条件交给builder,builder交给源,封装request

  1. // 声明源
  2. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  3. // 将查询建造器放入源,将高亮信息放入源,将聚合信息放入源
  4. searchSourceBuilder.query(boolQueryBuilder);
  5. searchSourceBuilder.highlighter(highlightBuilder);
  6. searchSourceBuilder.aggregation(aggregation);
  7. // 将源放入请求request
  8. SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);
  9. // 进行查询
  10. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
复制代码
3.3:效果解析和处理

  1. // 结果解析
  2. List<Object> ans = handleResult(response);
  3. // 结果处理
  4. for (Object o : ans) {
  5.     System.out.println(o.toString());
  6. }
  7. // todo: 结果解析封装
复制代码
  1. /**
  2. * 聚合查询
  3. */
  4. public void aggTest() {
  5.     // 声明查询请求,同时声明作用的索引(数据库)
  6.     SearchRequest request = new SearchRequest();
  7.     request.indices("person");
  8.     // 声明searchSourceBuilder源对象
  9.     SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  10.     // ======================== 源对象其他常见设置 ===========================
  11.     // 1. 排序和浅显分页
  12.     // from  & size 默认都是-1,也就是有多少显示多少
  13.     // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
  14.     searchSourceBuilder.from(0);
  15.     searchSourceBuilder.size(9999);
  16.     // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
  17.     searchSourceBuilder
  18.             .sort("age", SortOrder.DESC)
  19.             .sort("name", SortOrder.ASC);
  20.     // 构造查询建造器,根据不同的业务需求,进行不同的查询
  21.     // =============== 以boolean为例 ==================
  22.     BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
  23.             .must(QueryBuilders.termQuery("name.keyword", "王五"))
  24.             .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
  25.             // 可以进行bool嵌套
  26.             .must(QueryBuilders.boolQuery()
  27.                     .must(QueryBuilders.matchQuery("name", "张三"))
  28.             )
  29.             .boost(2.0f);
  30.     // 请求构造器加入源中
  31.     searchSourceBuilder.query(boolQueryBuilder);
  32.     // ============ 如果有高亮设置 ===============
  33.     HighlightBuilder highlightBuilder = new HighlightBuilder();
  34.     highlightBuilder.preTags("<font style='color: red'>");
  35.     highlightBuilder.postTags("</font>");
  36.     highlightBuilder.field("name").field("age");
  37.     //同一字段中存在多个高亮值 设置都高亮
  38.     highlightBuilder.requireFieldMatch(true);
  39.     // 高亮属性中加入源中
  40.     searchSourceBuilder.highlighter(highlightBuilder);
  41.     TermsAggregationBuilder aggregation =
  42.             // 进行分桶操作
  43.             AggregationBuilders.terms("Jone").field("name")
  44.                     // 对每一个分桶,进行子聚合
  45.                     .subAggregation(AggregationBuilders.avg("age_avg").field("age"));
  46.     // 将聚合结果放入源中
  47.     searchSourceBuilder.aggregation(aggregation);
  48.     // 进行查询
  49.     // 1. 设置查询源(source -> request)
  50.     request.source(searchSourceBuilder);
  51.     // 2. 通过search方法进行查询
  52.     List<Map<String, Object>> list = new ArrayList<>();
  53.     try {
  54.         SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  55.         // 结果处理
  56.         System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());
  57.         // 然后从这个searchResponse解析各种的值 -> 根据返回结构
  58.         int failedShards = response.getFailedShards();
  59.         System.out.println("失败的分片数:" + failedShards);
  60.         int successfulShards = response.getSuccessfulShards();
  61.         System.out.println("成功的分片数:" + successfulShards);
  62.         RestStatus status = response.status();
  63.         System.out.println("状态:" + status);
  64.         // 解析高亮数据
  65.         SearchHits hits = response.getHits();
  66.         System.out.println(hits.getMaxScore());
  67.         for (SearchHit hit : hits) {
  68.             System.out.println("fields: " + hit.getFields());
  69.             System.out.println("index is:" + hit.getIndex());
  70.             System.out.println("document field is: " + hit.getDocumentFields());
  71.             System.out.println("metadata field is: " + hit.getMetadataFields());
  72.             System.out.println("score is: " + hit.getScore());
  73.             System.out.println("-----------------");
  74.             // 原始数据,不包含高亮的数据
  75.             Map<String, Object> sourceMap = hit.getSourceAsMap();
  76.             // 高亮数据,拿到高亮字段name
  77.             Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
  78.             HighlightField highlightTitle = highlightFields.get("name");
  79.             // 将原始数据的name替换
  80.             if (highlightTitle != null) {
  81.                 Text[] fragments = highlightTitle.getFragments();
  82.                 if (fragments != null && fragments.length > 0) {
  83.                     sourceMap.replace("name", fragments[0].toString());
  84.                 }
  85.             }
  86.             // 循环将数据添加入列表
  87.             list.add(sourceMap);
  88.         }
  89.         list.forEach(System.out::println);
  90.     } catch (Exception e) {
  91.         e.printStackTrace();
  92.     }
  93. }
复制代码
三:repo & template

1:ElasticsearchRepository

1.1:repo先容

ElasticsearchRepository接口封装了Document的CRUD操作,我们直接界说接口继续它即可。

   ElasticsearchRepository接口的源码
  1. package org.springframework.data.elasticsearch.repository;
  2. import java.io.Serializable;
  3. import org.elasticsearch.index.query.QueryBuilder;
  4. import org.springframework.data.domain.Page;
  5. import org.springframework.data.domain.Pageable;
  6. import org.springframework.data.elasticsearch.core.query.SearchQuery;
  7. import org.springframework.data.repository.NoRepositoryBean;
  8. @NoRepositoryBean
  9. public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
  10.     <S extends T> S index(S entity);
  11.     Iterable<T> search(QueryBuilder query);
  12.     Page<T> search(QueryBuilder query, Pageable pageable);
  13.     Page<T> search(SearchQuery searchQuery);
  14.     Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
  15.     void refresh();
  16.     Class<T> getEntityClass();
  17. }
复制代码
  CrudRepository源码
  1. package org.springframework.data.repository;
  2. import java.util.Optional;
  3. /**
  4. * Interface for generic CRUD operations on a repository for a specific type.
  5. *
  6. * @author Oliver Gierke
  7. * @author Eberhard Wolff
  8. */
  9. @NoRepositoryBean
  10. public interface CrudRepository<T, ID> extends Repository<T, ID> {
  11.     // 保存相关
  12.     <S extends T> S save(S entity);
  13.     <S extends T> Iterable<S> saveAll(Iterable<S> entities);
  14.    
  15.     // 查找相关
  16.     Optional<T> findById(ID id);
  17.     boolean existsById(ID id);
  18.     Iterable<T> findAll();
  19.     Iterable<T> findAllById(Iterable<ID> ids);
  20.    
  21.     // 计数
  22.     long count();
  23.    
  24.     // delete相关
  25.     void deleteById(ID id);
  26.     void delete(T entity);
  27.     void deleteAll(Iterable<? extends T> entities);
  28.     void deleteAll();
  29. }
复制代码
  PagingAndSortingRepository源码
  1. @NoRepositoryBean
  2. public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> {
  3.     Iterable<T> findAll(Sort sort);
  4.     Page<T> findAll(Pageable pageable);
  5. }
复制代码
可见ElasticsearchRepository为我们封装好了许多常用的方法,我们可以在使用的时间直接调用这些方法举行操作
1.2:自界说方法命名规范

和所有的Spring Data一样,都支持指定格式的函数声明,对于这些函数,不用写详细的实现,而是可以直接调用
SpringData会通过动态代理的方式,帮我们天生基础的CRUD方法
   自界说方法命名规范
  

  • findBy[fieldName]:根据指定的单个条件举行等值查询;
  • findBy[fieldName]And[fieldName]And[...]:根据指定的多条件举行and查询;
  • findBy[fieldName]Or[fieldName]Or[...]:根据指定的多条件举行or查询;
  • findBy[fieldName]Equals:根据指定的单个条件举行等值查询;
  • findBy[fieldName]In:对指定的单个字段举行in查询,入参为一个列表;
  • findBy[fieldName]Like:对指定的单个字段举行like模糊查询;
  • findBy[fieldName]NotNull:查询指定字段不为空的数据;
  • findBy[fieldName]GreaterThan:对指定的单个字段举行]范围查询;
  • findBy[fieldName]GreaterThanEqual:对指定的单个字段举行]=范围查询;
  • findBy[fieldName]LessThan:对指定的单个字段举行[范围查询;
  • findBy[fieldName]LessThanEqual:对指定的单个字段举行[=范围查询;
  • Page[...] findBy[...]:根据指定的条件举行分页查询;
  • countBy[fieldName]:根据指定的条件字段举行计数统计;
  • findTop[n]By[fieldName]:根据指定字段做等值查询,并返回前n条数据;
  • findBy[fieldName]Between:根据指定字段举行between范围查询;
  • findDistinctBy[fieldName]:根据指定的单个条件举行去重查询;
  • findFirstBy[fieldName]:根据指定的单个条件举行等值查询(只返回满足条件的第一个数据);
  • findBy[fieldName1]OrderBy[fieldName2]:根据第一个字段做等值查询,并根据第二个字段做排序;
   各种方法名开头含义
  

  • 以get、find、read、query、stream开头,代表是查询数据的方法;
  • 以count开头,代表是计数统计的方法;
  • 以delete、remove开头,代表是删除数据的方法;
  • 以exists开头,代表是判断是否存在的方法;
  • 以search开头,代表是全文搜索的方法;
  • 以update开头,代表是修改数据的方法;
   方法名开头背面跟的关键字含义,以find开头的方法为例:
  

  • By:表示当火线法天生的查询语句,会根据By背面的逻辑来构成;
  • FirstBy:表示当火线法天生的语句,只会返回符合条件的第一条数据;
  • DistinctBy:表示当火线法天生的语句,会对符合条件的数据去重;
  • TopBy:表示当火线法天生的语句,只会返回符合条件的前N条数据;
  • [实体类名称]By:表示当火线法天生的语句,只会返回一条数据;
  • [实体类名称]sBy:表示当火线法天生的语句,会返回多条数据;
  • AllBy:表示当火线法天生的语句,会返回多条或所有数据;
  • DistinctFirstBy:表示当火线法天生的语句,只会返回去重后的第一条数据;
  • DistinctTopBy:表示当火线法天生的语句,只会返回去重后的前N条数据;
   方法名开头跟的关键字之后跟的是字段名[fieldName], 字段名称背面可以接的关键字如下
  在这些关键字之后,都是跟详细的字段名(实体类的属性名),字段名称背面可以接的关键字如下(同样以find为例):


  • Or:表示当前查询方法有多个条件,多个条件之间为“大概”关系;
  • And:表示当前查询方法有多个条件,多个条件之间为“并且”关系;
  • OrderBy:表示当前查询会涉及到排序,背面必要跟一个排序字段;
  • Between:表示当火线法为between范围查询;
  • GreaterThan:表示当火线法为>查询;
  • GreaterThanEqual:表示当火线法为>=查询;
  • LessThan:表示当火线法为 < 查询;
  • LessThanEqual:表示当火线法为<=查询;
  • After:和GreaterThan差不多,相当于查询指定数值之后的数据;
  • Before:和LessThan差不多,查询指定条件之前的数据;
  • Containing:查询某字段中包含指定字符的数据;
  • Empty:表示当火线法会查询指定字段为空的数据,与之含义类似的另有Null、Exists;
  • Equals:表示当火线法会根据指定字段做等值查询;
  • Is:和Equals差不多;
  • In:表示当火线法为in多值匹配查询;
  • Like:表示当火线法为like模糊查询;
  • Not:可以和上述大多数关键字组合,带有Not的则含义相反,如NotEmpty表示不为空;


2:ElasticsearchRestTemplate

2.1:template先容

   template提供了浩繁模板方法,只要我们编写好对应的条件,然后调用模板方法即可
  


2.2:template查询操作

下面是操作流程【查询为例】
2.2.1:入参特别情况处理【包罗异常入参】

2.2.2:构建查询条件Criteria

  1. // 1:构建查询条件
  2. Criteria criteria = new Criteria()
  3.     // 条件一:xxxx
  4.     .and(new Criteria("字段名称").contains(条件中的内容))
  5.     // 条件二:xxx
  6.     .and(new Criteria("字段名称").is(条件中的内容));
  7. // 2:封装进入到CriteriaQuery
  8. CriteriaQuery filter = new CriteriaQuery(criteria);
  9. // 3:设置分页信息【可能没有,看业务】
  10. CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
复制代码
2.2.3:构建高亮条件

  1. // 1:声明高亮构造器对象
  2. HighlightBuilder highlightBuilder = new HighlightBuilder();
  3. // todo: 设置高亮领域
  4. // todo: 前置后置标签
  5. // 2:构建高亮query
  6. HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
  7. // 3:将高亮query封装进criteriaQuery中
  8. criteriaQuery.setHighlightQuery(highlightQuery);
复制代码
2.2.4:查询(也可能是其他的方法,不一定是search)

  1. elasticsearchRestTemplate.search(查询条件,返回信息的实体.class);
复制代码
2.2.5:效果封装

  1. // 对结果进行封装
复制代码
2.3:创建索引和删除索引

对于索引的创建和删除,建议照旧直接使用Kibana举行相关的操作,假如要使用template,可以输入对应的索引名称举行创建
template会到实体中找到@Document(indexName = "xxx")对应的信息,根据这个注解的设置拿到settings对应的信息设置
然后根据实体类标注的字段类型和分析器类型创建对应的mappings对应的信息设置
  1. package com.example.es_demo.pojo;
  2. import com.alibaba.fastjson.annotation.JSONField;
  3. import com.fasterxml.jackson.annotation.JsonFormat;
  4. import lombok.AllArgsConstructor;
  5. import lombok.Data;
  6. import lombok.NoArgsConstructor;
  7. import org.springframework.data.annotation.Id;
  8. import org.springframework.data.elasticsearch.annotations.DateFormat;
  9. import org.springframework.data.elasticsearch.annotations.Document;
  10. import org.springframework.data.elasticsearch.annotations.Field;
  11. import org.springframework.data.elasticsearch.annotations.FieldType;
  12. import java.time.LocalDateTime;
  13. /**
  14. * <p>
  15. * 功能描述:实体类
  16. * </p>
  17. *
  18. * @author cui haida
  19. * @date 2024/01/28/8:20
  20. */
  21. @NoArgsConstructor
  22. @AllArgsConstructor
  23. @Data
  24. @Document(indexName = "mytest") // 指定对应的索引名称
  25. public class MyTest {
  26.     @Id
  27.     @JSONField(serialize = false)
  28.     private String id;
  29.     @Field(type = FieldType.Keyword)
  30.     private String name;
  31.     @Field(type = FieldType.Keyword)
  32.     private String age;
  33.     // text类型,并使用IK最粗粒度的分词器,检索时的分词器采用的是最细粒度的IK分词器
  34.     @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
  35.     private String address;
  36.     // 注意日期格式的特殊处理
  37.     @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
  38.     @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
  39.     private LocalDateTime createTime;
  40. }
复制代码
  1. public String create(String indexName) {
  2.     IndexOperations indexOperations =
  3.         elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
  4.     if (indexOperations.exists()) {
  5.         return "索引已存在";
  6.     }
  7.     indexOperations.create();
  8.     return "索引创建成功";
  9. }
复制代码
  1. public String delete(String indexName) {
  2.     IndexOperations indexOperations =
  3.         elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
  4.     indexOperations.delete();
  5.     return "索引删除成功";
  6. }
复制代码
3:使用实例

  1. package com.cui.es_demo.client;
  2. import com.cui.es_demo.client.repo.PersonRepository;
  3. import com.cui.es_demo.entity.PageResponse;
  4. import com.cui.es_demo.entity.Person;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.data.domain.Page;
  8. import org.springframework.data.domain.PageRequest;
  9. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
  10. import org.springframework.data.elasticsearch.core.SearchHit;
  11. import org.springframework.data.elasticsearch.core.SearchHits;
  12. import org.springframework.data.elasticsearch.core.query.Criteria;
  13. import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
  14. import org.springframework.stereotype.Service;
  15. import java.util.ArrayList;
  16. import java.util.List;
  17. import java.util.stream.Collectors;
  18. /**
  19. * @author cui haida
  20. * 2025/1/30
  21. */
  22. @Service
  23. @Slf4j
  24. public class TemplateDemo {
  25.     // es repo -> 这里提供了Spring Data的基本方法,可以直接使用
  26.     @Autowired
  27.     PersonRepository personRepository;
  28.     // template
  29.     @Autowired
  30.     ElasticsearchRestTemplate elasticsearchRestTemplate;
  31.     // =========== repo测试 =============
  32.     // 就是直接调用对应的方法,只要语法满足上面说的Spring Data语法就可以
  33.     public void saveAll(List<Person> orders) {
  34.         personRepository.saveAll(orders);
  35.     }
  36.     public void deleteById(Integer id) {
  37.         personRepository.deleteById(id);
  38.     }
  39.     public void updateById(Person person) {
  40.         personRepository.save(person);
  41.     }
  42.     public Person findById(Integer id) {
  43.         return (Person) personRepository.findById(id).orElse(null);
  44.     }
  45.     public PageResponse<Person> findAll(Integer pageIndex, Integer pageSize) {
  46.         Page<Person> page = personRepository.findAll(PageRequest.of(pageIndex, pageSize));
  47.         PageResponse<Person> pageResponse = new PageResponse<Person>();
  48.         pageResponse.setTotal(page.getTotalElements());
  49.         pageResponse.setResult(page.getContent());
  50.         return pageResponse;
  51.     }
  52.     // =============== template测试 ==============
  53.     // 1:构造查询条件
  54.     // 2:调用template.xxx(条件)
  55.     // 3:结果处理(封装)
  56.     public PageResponse<Person> findList(Person person, Integer pageIndex, Integer pageSize) {
  57.         // 1:构建查询条件
  58.         Criteria criteria = new Criteria()
  59.                 // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
  60.                 .and(new Criteria("orderDesc").contains(person.getAddress()))
  61.                 // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
  62.                 .and(new Criteria("orderNo").is(person.getId()));
  63.         // 封装进入到CriteriaQuery
  64.         CriteriaQuery filter = new CriteriaQuery(criteria);
  65.         // 设置分页信息
  66.         CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
  67.         // 2:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
  68.         SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);
  69.         // 3:结果封装
  70.         List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
  71.         PageResponse<Person> pageResponse = new PageResponse<>();
  72.         pageResponse.setTotal(searchHits.getTotalHits());
  73.         pageResponse.setResult(result);
  74.         return pageResponse;
  75.     }
  76.     public PageResponse<Person> findHighlight(Person person, Integer pageIndex, Integer pageSize) {
  77.         // 0:特殊情况的处理
  78.         if (person == null) {
  79.             PageResponse<Person> pageResponse = new PageResponse<Person>();
  80.             pageResponse.setTotal(0L);
  81.             pageResponse.setResult(new ArrayList<>());
  82.             return pageResponse;
  83.         }
  84.         // 1:构建查询条件
  85.         Criteria criteria = new Criteria()
  86.                 // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
  87.                 .and(new Criteria("id").contains(person.getId()))
  88.                 // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
  89.                 .and(new Criteria("address").is(person.getAddress()));
  90.         // 封装进入到CriteriaQuery
  91.         CriteriaQuery filter = new CriteriaQuery(criteria);
  92.         // 设置分页信息
  93.         CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
  94.         // 3:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
  95.         SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);
  96.         // 4:结果封装
  97.         // 对每一个都设置对应的高亮字段信息
  98.         List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
  99.         // 封装页码信息
  100.         PageResponse<Person> pageResponse = new PageResponse<>();
  101.         pageResponse.setTotal(searchHits.getTotalHits());
  102.         pageResponse.setResult(result);
  103.         return pageResponse;
  104.     }
  105. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

宁睿

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表