Spring boot 集成 ElasticSearch
Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用 JSON作为文档序列化的格式
- {
- "name" : "John",
- "sex" : "Male",
- "age" : 25,
- "birthDate": "1990/05/01",
- "about" : "I love to go rock climbing",
- "interests": [ "sports", "music" ]
- }
复制代码 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
- <dependencies>
- <!-- spring boot启动器 -->
- <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <!-- spring boot web启动器 -->
- <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!-- spring boot 测试启动器 -->
- <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- lombok -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
- <!-- commons-lang3工具类 -->
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- </dependency>
- <!-- spring data elasticsearch -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
- </dependency>
- <!-- es 高阶客户端 -->
- <dependency>
- <groupId>org.elasticsearch.client</groupId>
- <artifactId>elasticsearch-rest-high-level-client</artifactId>
- </dependency>
- <!-- es 低阶客户端 -->
- <dependency>
- <groupId>org.elasticsearch.client</groupId>
- <artifactId>transport</artifactId>
- </dependency>
- </dependencies>
复制代码- spring:
- elasticsearch:
- rest:
- uris: http://localhost:9200,http://localhost:9201,http://localhost:9202 # 集群地址
- connection-timeout: 5s # 连接超时时间
- read-timeout: 30s # 读取超时时间
- max-connections: 100 # 最大连接数
- max-connections-per-route: 20 # 每个路由的最大连接数
- password: es_password
复制代码 2:客户端连接相关构建
- package com.cui.es_demo.config;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.elasticsearch.client.ClientConfiguration;
- import org.springframework.data.elasticsearch.client.RestClients;
- import java.util.Arrays;
- /**
- * @author cui haida
- * 2025/1/29
- */
- @Configuration
- @ConfigurationProperties(prefix = "spring.elasticsearch.rest")
- public class ElasticSearchClientConfig {
- /**
- * 集群地址
- */
- private String uris;
- /**
- * 用户名和密码
- */
- private String username;
- private String password;
- @Bean
- public RestHighLevelClient restHighLevelClient() {
- // 创建连接, 指定url和用户名密码
- ClientConfiguration build = ClientConfiguration.builder()
- .connectedTo(uris)
- .withBasicAuth(username, password)
- .build();
- return RestClients.create(build).rest();
- }
- }
复制代码 3:实体类相关注解设置说明
- package com.cui.es_demo.entity;
- import com.fasterxml.jackson.annotation.JsonFormat;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import org.springframework.data.annotation.Id;
- import org.springframework.data.elasticsearch.annotations.DateFormat;
- import org.springframework.data.elasticsearch.annotations.Document;
- import org.springframework.data.elasticsearch.annotations.Field;
- import org.springframework.data.elasticsearch.annotations.FieldType;
- import java.time.LocalDateTime;
- /**
- * 实体类相关注解介绍
- * - @Document:指定索引库的名称,以及分片数、副本数、刷新间隔、是否允许创建索引
- * - @Id:指定主键
- * - @Field:指定字段的名称,以及字段的分词器,以及字段的存储类型,以及字段的分词器
- * - @JsonFormat:指定日期格式(注意日期类型字段不要用java.util.Date类型,要用java.time.LocalDate或java.time.LocalDateTime类型)
- * @author cui haida
- * 2025/1/30
- */
- @NoArgsConstructor
- @AllArgsConstructor
- @Data
- // 指定对应的索引名称,, 主分片数 = 3, 副本数 = 1, 刷新间隔 = 1s, 允许创建索引
- @Document(indexName = "person", shards = 3, replicas = 1, refreshInterval = "1s", createIndex = true)
- public class Person {
- @Id
- private String id;
- @Field(type = FieldType.Keyword)
- private String name;
- @Field(type = FieldType.Keyword)
- private String age;
- // text类型,并使用IK最粗粒度的分词器
- // 检索时的分词器采用的是最细粒度的IK分词器
- @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
- private String address;
- // 注意日期格式的特殊处理
- // 指定格式化和时区
- @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
- @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
- private LocalDateTime createTime;
- }
复制代码 二:客户端client相关操作说明
对索引相关的操作都不推荐在这里执行,建议直接使用Kibana
文档的更新和插入操作建议使用repo & template方式
这里只先容查询相关操作
1:检索流程
1.1:构建请求request和源source
- // 声明查询请求对象
- SearchRequest client = new SearchRequest();
- // 指定使用的索引(数据库)
- client.indices(index);
- // 声明searchSourceBuilder源对象
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- // ======================== 源对象其他常见设置 ===========================
- // 1. 排序和浅显分页
- // from & size 默认都是-1,也就是有多少显示多少
- // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
- searchSourceBuilder.from(0);
- searchSourceBuilder.size(9999);
- // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
- searchSourceBuilder
- .sort("age", SortOrder.DESC)
- .sort("name", SortOrder.ASC);
复制代码 1.2:查询建造器queryBuilder构建
- // =============== 以boolean为例 ==================
- BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
- .must(QueryBuilders.termQuery("name.keyword", "王五"))
- .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
- // 可以进行bool嵌套
- .must(QueryBuilders.boolQuery()
- .must(QueryBuilders.matchQuery("name", "张三"))
- )
- .boost(2.0f);
- // 加入对应的builder到searchSourceBuilder中
- searchSourceBuilder.query(boolQueryBuilder);
- // ============ 如果有高亮设置 ===============
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- highlightBuilder.preTags("<font style='color: red'>");
- highlightBuilder.postTags("</font>");
- highlightBuilder.field("name").field("age");
- //同一字段中存在多个高亮值 设置都高亮
- highlightBuilder.requireFieldMatch(true);
- // 高亮属性中加入高亮配置器
- searchSourceBuilder.highlighter(highlightBuilder);
复制代码 1.3:请求建造器参加到请求源,请求源参加到请求中
- searchSourceBuilder.query(xxxQueryBuilder);
- // 封装request,指定要request的索引index,指定source -> searchSourceBuilder
- SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);
- // 通过client.search获取响应
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
复制代码 1.4:效果hits和highlight分析
- List<Map<String, Object>> list = new ArrayList<>();
- SearchResponse searchResponse = this.client.search(client, RequestOptions.DEFAULT);
- if (searchResponse == null) {
- log.warn("没有返回值");
- return;
- }
- // todo: 然后从这个searchResponse解析各种的值 -> 根据返回结构
- int failedShards = searchResponse.getFailedShards();
- System.out.println("失败的分片数:" + failedShards);
- int successfulShards = searchResponse.getSuccessfulShards();
- System.out.println("成功的分片数:" + successfulShards);
- RestStatus status = searchResponse.status();
- System.out.println("状态:" + status);
- //解析高亮数据
- SearchHits hits = searchResponse.getHits();
- System.out.println(hits.getMaxScore());
- for (SearchHit hit : hits) {
- System.out.println("fields: " + hit.getFields());
- System.out.println("index is:" + hit.getIndex());
- System.out.println("document field is: " + hit.getDocumentFields());
- System.out.println("metadata field is: " + hit.getMetadataFields());
- System.out.println("score is: " + hit.getScore());
- System.out.println("-----------------");
- //原始数据,不包含高亮的数据
- Map<String, Object> sourceMap = hit.getSourceAsMap();
- //高亮数据,拿到高亮字段name
- Map<String, HighlightField> highlightFields = hit.getHighlightFields();
- HighlightField highlightTitle = highlightFields.get("name");
- //将原始数据的name替换
- if (highlightTitle != null) {
- Text[] fragments = highlightTitle.getFragments();
- if (fragments != null && fragments.length > 0) {
- sourceMap.replace("name", fragments[0].toString());
- }
- }
- list.add(sourceMap);//循环将数据添加入列表
- }
- list.forEach(System.out::println);
复制代码- package com.cui.es_demo.client;
- import lombok.extern.slf4j.Slf4j;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.text.Text;
- import org.elasticsearch.index.query.BoolQueryBuilder;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.rest.RestStatus;
- import org.elasticsearch.search.SearchHit;
- import org.elasticsearch.search.SearchHits;
- import org.elasticsearch.search.builder.SearchSourceBuilder;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.stereotype.Service;
- import javax.annotation.Resource;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- /**
- * 客户端相关操作
- *
- * @author cui haida
- * 2025/1/30
- */
- @Service
- @Slf4j
- public class ClientDemo {
- //
- private final RestHighLevelClient client;
- public ClientDemo(RestHighLevelClient restHighLevelClient) {
- this.client = restHighLevelClient;
- }
- /**
- * 主要介绍客户端查询操作
- */
- public void searchTest(String[] args) {
- // 声明查询请求,同时声明作用的索引(数据库)
- SearchRequest request = new SearchRequest();
- request.indices("person");
- // 声明searchSourceBuilder源对象
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- // ======================== 源对象其他常见设置 ===========================
- // 1. 排序和浅显分页
- // from & size 默认都是-1,也就是有多少显示多少
- // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
- searchSourceBuilder.from(0);
- searchSourceBuilder.size(9999);
- // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
- searchSourceBuilder
- .sort("age", SortOrder.DESC)
- .sort("name", SortOrder.ASC);
- // 构造查询建造器,根据不同的业务需求,进行不同的查询
- // =============== 以boolean为例 ==================
- BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
- .must(QueryBuilders.termQuery("name.keyword", "王五"))
- .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
- // 可以进行bool嵌套
- .must(QueryBuilders.boolQuery()
- .must(QueryBuilders.matchQuery("name", "张三"))
- )
- .boost(2.0f);
- // 请求构造器加入源中
- searchSourceBuilder.query(boolQueryBuilder);
- // ============ 如果有高亮设置 ===============
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- highlightBuilder.preTags("<font style='color: red'>");
- highlightBuilder.postTags("</font>");
- highlightBuilder.field("name").field("age");
- //同一字段中存在多个高亮值 设置都高亮
- highlightBuilder.requireFieldMatch(true);
- // 高亮属性中加入源中
- searchSourceBuilder.highlighter(highlightBuilder);
- // 进行查询
- // 1. 设置查询源(source -> request)
- request.source(searchSourceBuilder);
- // 2. 通过search方法进行查询
- List<Map<String, Object>> list = new ArrayList<>();
- try {
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 结果处理
- System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());
- // 然后从这个searchResponse解析各种的值 -> 根据返回结构
- int failedShards = response.getFailedShards();
- System.out.println("失败的分片数:" + failedShards);
- int successfulShards = response.getSuccessfulShards();
- System.out.println("成功的分片数:" + successfulShards);
- RestStatus status = response.status();
- System.out.println("状态:" + status);
- // 解析高亮数据
- SearchHits hits = response.getHits();
- System.out.println(hits.getMaxScore());
- for (SearchHit hit : hits) {
- System.out.println("fields: " + hit.getFields());
- System.out.println("index is:" + hit.getIndex());
- System.out.println("document field is: " + hit.getDocumentFields());
- System.out.println("metadata field is: " + hit.getMetadataFields());
- System.out.println("score is: " + hit.getScore());
- System.out.println("-----------------");
- // 原始数据,不包含高亮的数据
- Map<String, Object> sourceMap = hit.getSourceAsMap();
- // 高亮数据,拿到高亮字段name
- Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
- HighlightField highlightTitle = highlightFields.get("name");
- // 将原始数据的name替换
- if (highlightTitle != null) {
- Text[] fragments = highlightTitle.getFragments();
- if (fragments != null && fragments.length > 0) {
- sourceMap.replace("name", fragments[0].toString());
- }
- }
- // 循环将数据添加入列表
- list.add(sourceMap);
- }
- list.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
复制代码 2:各种查询构造器说明
2.1:MatchQueryBuilder
- MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("name", "李四")
- // 李和四必须都出现在name字段中才可以, operator可以是OR
- .operator(Operator.AND)
- // 匹配度必须是 >= 75%才行
- .minimumShouldMatch("75%")
- // 是否忽略数据类型转换异常
- .lenient(true);
复制代码 2.2:RegexQueryBuilder
- // 张姓开头的doc
- RegexpQueryBuilder regexpQueryBuilder =
- QueryBuilders.regexpQuery("name", "张*").caseInsensitive(true);
复制代码 2.3:IdsQueryBuilder
- String[] ids = new String[]{"1", "2", "3"};
- IdsQueryBuilder idsQueryBuilder =
- QueryBuilders.idsQuery().addIds(ids);
复制代码 2.4:MatchPhraseQueryBuilder
- MatchPhraseQueryBuilder match =
- QueryBuilders.matchPhraseQuery("name", "李四").slop(2);
复制代码 2.5:MatchPhrasePrefixQueryBuilder
- MatchPhrasePrefixQueryBuilder matchPhrasePrefixQueryBuilder =
- QueryBuilders.matchPhrasePrefixQuery("name", "张").maxExpansions(1);
复制代码 2.6:MultiMatchQueryBuilder
- String[] fieldNames = new String[]{"name", "age"};
- String searchText = "老坛";
- MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(searchText, fieldNames)
- // 最多数量匹配
- .type(MultiMatchQueryBuilder.Type.MOST_FIELDS)
- // 使用and操作符和minimum_should_match参数来减少相关度低的文档数量
- .operator(Operator.AND);
复制代码 2.7:TermQueryBuilder
- TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name.keyword", "李四");
- TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name.keyword", "李四光", "李四");
复制代码 2.8:FuzzyQueryBuilder
- FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "李四");
复制代码 2.9:RangeQueryBuilder
- RangeQueryBuilder ageRangeFilter = QueryBuilders.rangeQuery("age")
- // greater than 12
- .gt(12)
- // less than 17
- .lt(17);
- // 默认是true包含头尾,设置false去掉头尾
- RangeQueryBuilder ageRangeFilter2 = QueryBuilders.rangeQuery("age")
- .from(12)
- .to(17)
- // 不包含最后一个元素
- .includeLower(false)
- // 包含第一个元素
- .includeUpper(true);
复制代码 2.10:WildcardQueryBuilder
- // wildcard 通配符查询, 支持*,匹配任何字符序列, 包括空,避免*
- String queryString = "Lc*dd";
- WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("name", queryString);
复制代码 2.11:BoolQueryBuilder
- BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
- .must(QueryBuilders.termQuery("name.keyword", "王五"))
- .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
- // 可以进行bool嵌套
- .must(QueryBuilders.boolQuery()
- .must(QueryBuilders.matchQuery("name", "张三"))
- )
- .boost(2.0f);
- searchSourceBuilder.query(boolQueryBuilder);
复制代码 2.12:其他的QueryBuilder
- // 16: 其他查询
- // moreLikeThisQuery: 实现基于内容推荐, 支持实现一句话相似文章查询
- // percent_terms_to_match:匹配项(term)的百分比,默认是0.3
- // min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
- // max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25
- // stop_words:设置停止词,匹配时会忽略停止词
- // min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制
- // max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制
- // min_word_len:最小的词语长度,默认是0
- // max_word_len:最多的词语长度,默认无限制
- // boost_terms:设置词语权重,默认是1
- // boost:设置查询权重,默认是1
- // analyzer:设置使用的分词器,默认是使用该字段指定的分词器
- QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery(new String[]{"王"})
- // 一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2
- .minTermFreq(1)
- // 一条查询语句中允许最多查询词语的个数,默认是25
- .maxQueryTerms(3);
- // 查询条件
- searchSourceBuilder.query(matchQueryBuilder)
- // 后置过滤器,id, exists, term, range
- .postFilter(QueryBuilders.existsQuery("tag"));
复制代码 3:聚合建造器说明
3.1:构造聚合条件
- TermsAggregationBuilder aggregation =
- AggregationBuilders.terms(等于的值).field(字段名称) // 进行分桶操作
- .subAggregation(AggregationBuilders.avg(桶的名字,随便起的).field(根据xx字段分桶)) // 对每一个分桶,进行子聚合
复制代码 3.2:将聚合条件交给builder,builder交给源,封装request
- // 声明源
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- // 将查询建造器放入源,将高亮信息放入源,将聚合信息放入源
- searchSourceBuilder.query(boolQueryBuilder);
- searchSourceBuilder.highlighter(highlightBuilder);
- searchSourceBuilder.aggregation(aggregation);
- // 将源放入请求request
- SearchRequest request = new SearchRequest(index).source(searchSourceBuilder);
- // 进行查询
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
复制代码 3.3:效果解析和处理
- // 结果解析
- List<Object> ans = handleResult(response);
- // 结果处理
- for (Object o : ans) {
- System.out.println(o.toString());
- }
- // todo: 结果解析封装
复制代码- /**
- * 聚合查询
- */
- public void aggTest() {
- // 声明查询请求,同时声明作用的索引(数据库)
- SearchRequest request = new SearchRequest();
- request.indices("person");
- // 声明searchSourceBuilder源对象
- SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
- // ======================== 源对象其他常见设置 ===========================
- // 1. 排序和浅显分页
- // from & size 默认都是-1,也就是有多少显示多少
- // from-size浅分页适合数据量不大的情况(官网推荐是数据少于10000条),可以跳码进行查询
- searchSourceBuilder.from(0);
- searchSourceBuilder.size(9999);
- // 2. 排序, date、float 等类型添加排序, text类型的字段不允许排序
- searchSourceBuilder
- .sort("age", SortOrder.DESC)
- .sort("name", SortOrder.ASC);
- // 构造查询建造器,根据不同的业务需求,进行不同的查询
- // =============== 以boolean为例 ==================
- BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
- .must(QueryBuilders.termQuery("name.keyword", "王五"))
- .mustNot(QueryBuilders.idsQuery().addIds("1", "2", "3"))
- // 可以进行bool嵌套
- .must(QueryBuilders.boolQuery()
- .must(QueryBuilders.matchQuery("name", "张三"))
- )
- .boost(2.0f);
- // 请求构造器加入源中
- searchSourceBuilder.query(boolQueryBuilder);
- // ============ 如果有高亮设置 ===============
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- highlightBuilder.preTags("<font style='color: red'>");
- highlightBuilder.postTags("</font>");
- highlightBuilder.field("name").field("age");
- //同一字段中存在多个高亮值 设置都高亮
- highlightBuilder.requireFieldMatch(true);
- // 高亮属性中加入源中
- searchSourceBuilder.highlighter(highlightBuilder);
- TermsAggregationBuilder aggregation =
- // 进行分桶操作
- AggregationBuilders.terms("Jone").field("name")
- // 对每一个分桶,进行子聚合
- .subAggregation(AggregationBuilders.avg("age_avg").field("age"));
- // 将聚合结果放入源中
- searchSourceBuilder.aggregation(aggregation);
- // 进行查询
- // 1. 设置查询源(source -> request)
- request.source(searchSourceBuilder);
- // 2. 通过search方法进行查询
- List<Map<String, Object>> list = new ArrayList<>();
- try {
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 结果处理
- System.out.println("response.getHits().getTotalHits() = " + response.getHits().getTotalHits());
- // 然后从这个searchResponse解析各种的值 -> 根据返回结构
- int failedShards = response.getFailedShards();
- System.out.println("失败的分片数:" + failedShards);
- int successfulShards = response.getSuccessfulShards();
- System.out.println("成功的分片数:" + successfulShards);
- RestStatus status = response.status();
- System.out.println("状态:" + status);
- // 解析高亮数据
- SearchHits hits = response.getHits();
- System.out.println(hits.getMaxScore());
- for (SearchHit hit : hits) {
- System.out.println("fields: " + hit.getFields());
- System.out.println("index is:" + hit.getIndex());
- System.out.println("document field is: " + hit.getDocumentFields());
- System.out.println("metadata field is: " + hit.getMetadataFields());
- System.out.println("score is: " + hit.getScore());
- System.out.println("-----------------");
- // 原始数据,不包含高亮的数据
- Map<String, Object> sourceMap = hit.getSourceAsMap();
- // 高亮数据,拿到高亮字段name
- Map<String, org.elasticsearch.search.fetch.subphase.highlight.HighlightField> highlightFields = hit.getHighlightFields();
- HighlightField highlightTitle = highlightFields.get("name");
- // 将原始数据的name替换
- if (highlightTitle != null) {
- Text[] fragments = highlightTitle.getFragments();
- if (fragments != null && fragments.length > 0) {
- sourceMap.replace("name", fragments[0].toString());
- }
- }
- // 循环将数据添加入列表
- list.add(sourceMap);
- }
- list.forEach(System.out::println);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
复制代码 三:repo & template
1:ElasticsearchRepository
1.1:repo先容
ElasticsearchRepository接口封装了Document的CRUD操作,我们直接界说接口继续它即可。
ElasticsearchRepository接口的源码
- package org.springframework.data.elasticsearch.repository;
- import java.io.Serializable;
- import org.elasticsearch.index.query.QueryBuilder;
- import org.springframework.data.domain.Page;
- import org.springframework.data.domain.Pageable;
- import org.springframework.data.elasticsearch.core.query.SearchQuery;
- import org.springframework.data.repository.NoRepositoryBean;
- @NoRepositoryBean
- public interface ElasticsearchRepository<T, ID extends Serializable> extends ElasticsearchCrudRepository<T, ID> {
- <S extends T> S index(S entity);
- Iterable<T> search(QueryBuilder query);
- Page<T> search(QueryBuilder query, Pageable pageable);
- Page<T> search(SearchQuery searchQuery);
- Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);
- void refresh();
- Class<T> getEntityClass();
- }
复制代码 CrudRepository源码
- package org.springframework.data.repository;
- import java.util.Optional;
- /**
- * Interface for generic CRUD operations on a repository for a specific type.
- *
- * @author Oliver Gierke
- * @author Eberhard Wolff
- */
- @NoRepositoryBean
- public interface CrudRepository<T, ID> extends Repository<T, ID> {
- // 保存相关
- <S extends T> S save(S entity);
- <S extends T> Iterable<S> saveAll(Iterable<S> entities);
-
- // 查找相关
- Optional<T> findById(ID id);
- boolean existsById(ID id);
- Iterable<T> findAll();
- Iterable<T> findAllById(Iterable<ID> ids);
-
- // 计数
- long count();
-
- // delete相关
- void deleteById(ID id);
- void delete(T entity);
- void deleteAll(Iterable<? extends T> entities);
- void deleteAll();
- }
复制代码 PagingAndSortingRepository源码
- @NoRepositoryBean
- public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> {
- Iterable<T> findAll(Sort sort);
- Page<T> findAll(Pageable pageable);
- }
复制代码 可见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:构建查询条件
- Criteria criteria = new Criteria()
- // 条件一:xxxx
- .and(new Criteria("字段名称").contains(条件中的内容))
- // 条件二:xxx
- .and(new Criteria("字段名称").is(条件中的内容));
- // 2:封装进入到CriteriaQuery
- CriteriaQuery filter = new CriteriaQuery(criteria);
- // 3:设置分页信息【可能没有,看业务】
- CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
复制代码 2.2.3:构建高亮条件
- // 1:声明高亮构造器对象
- HighlightBuilder highlightBuilder = new HighlightBuilder();
- // todo: 设置高亮领域
- // todo: 前置后置标签
- // 2:构建高亮query
- HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
- // 3:将高亮query封装进criteriaQuery中
- criteriaQuery.setHighlightQuery(highlightQuery);
复制代码 2.2.4:查询(也可能是其他的方法,不一定是search)
- elasticsearchRestTemplate.search(查询条件,返回信息的实体.class);
复制代码 2.2.5:效果封装
2.3:创建索引和删除索引
对于索引的创建和删除,建议照旧直接使用Kibana举行相关的操作,假如要使用template,可以输入对应的索引名称举行创建
template会到实体中找到@Document(indexName = "xxx")对应的信息,根据这个注解的设置拿到settings对应的信息设置
然后根据实体类标注的字段类型和分析器类型创建对应的mappings对应的信息设置
- package com.example.es_demo.pojo;
- import com.alibaba.fastjson.annotation.JSONField;
- import com.fasterxml.jackson.annotation.JsonFormat;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import org.springframework.data.annotation.Id;
- import org.springframework.data.elasticsearch.annotations.DateFormat;
- import org.springframework.data.elasticsearch.annotations.Document;
- import org.springframework.data.elasticsearch.annotations.Field;
- import org.springframework.data.elasticsearch.annotations.FieldType;
- import java.time.LocalDateTime;
- /**
- * <p>
- * 功能描述:实体类
- * </p>
- *
- * @author cui haida
- * @date 2024/01/28/8:20
- */
- @NoArgsConstructor
- @AllArgsConstructor
- @Data
- @Document(indexName = "mytest") // 指定对应的索引名称
- public class MyTest {
- @Id
- @JSONField(serialize = false)
- private String id;
- @Field(type = FieldType.Keyword)
- private String name;
- @Field(type = FieldType.Keyword)
- private String age;
- // text类型,并使用IK最粗粒度的分词器,检索时的分词器采用的是最细粒度的IK分词器
- @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_max_word")
- private String address;
- // 注意日期格式的特殊处理
- @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
- @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
- private LocalDateTime createTime;
- }
复制代码- public String create(String indexName) {
- IndexOperations indexOperations =
- elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
- if (indexOperations.exists()) {
- return "索引已存在";
- }
- indexOperations.create();
- return "索引创建成功";
- }
复制代码- public String delete(String indexName) {
- IndexOperations indexOperations =
- elasticsearchRestTemplate.indexOps(IndexCoordinates.of(indexName));
- indexOperations.delete();
- return "索引删除成功";
- }
复制代码 3:使用实例
- package com.cui.es_demo.client;
- import com.cui.es_demo.client.repo.PersonRepository;
- import com.cui.es_demo.entity.PageResponse;
- import com.cui.es_demo.entity.Person;
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.domain.Page;
- import org.springframework.data.domain.PageRequest;
- import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
- import org.springframework.data.elasticsearch.core.SearchHit;
- import org.springframework.data.elasticsearch.core.SearchHits;
- import org.springframework.data.elasticsearch.core.query.Criteria;
- import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
- import org.springframework.stereotype.Service;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.stream.Collectors;
- /**
- * @author cui haida
- * 2025/1/30
- */
- @Service
- @Slf4j
- public class TemplateDemo {
- // es repo -> 这里提供了Spring Data的基本方法,可以直接使用
- @Autowired
- PersonRepository personRepository;
- // template
- @Autowired
- ElasticsearchRestTemplate elasticsearchRestTemplate;
- // =========== repo测试 =============
- // 就是直接调用对应的方法,只要语法满足上面说的Spring Data语法就可以
- public void saveAll(List<Person> orders) {
- personRepository.saveAll(orders);
- }
- public void deleteById(Integer id) {
- personRepository.deleteById(id);
- }
- public void updateById(Person person) {
- personRepository.save(person);
- }
- public Person findById(Integer id) {
- return (Person) personRepository.findById(id).orElse(null);
- }
- public PageResponse<Person> findAll(Integer pageIndex, Integer pageSize) {
- Page<Person> page = personRepository.findAll(PageRequest.of(pageIndex, pageSize));
- PageResponse<Person> pageResponse = new PageResponse<Person>();
- pageResponse.setTotal(page.getTotalElements());
- pageResponse.setResult(page.getContent());
- return pageResponse;
- }
- // =============== template测试 ==============
- // 1:构造查询条件
- // 2:调用template.xxx(条件)
- // 3:结果处理(封装)
- public PageResponse<Person> findList(Person person, Integer pageIndex, Integer pageSize) {
- // 1:构建查询条件
- Criteria criteria = new Criteria()
- // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
- .and(new Criteria("orderDesc").contains(person.getAddress()))
- // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
- .and(new Criteria("orderNo").is(person.getId()));
- // 封装进入到CriteriaQuery
- CriteriaQuery filter = new CriteriaQuery(criteria);
- // 设置分页信息
- CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
- // 2:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
- SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);
- // 3:结果封装
- List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
- PageResponse<Person> pageResponse = new PageResponse<>();
- pageResponse.setTotal(searchHits.getTotalHits());
- pageResponse.setResult(result);
- return pageResponse;
- }
- public PageResponse<Person> findHighlight(Person person, Integer pageIndex, Integer pageSize) {
- // 0:特殊情况的处理
- if (person == null) {
- PageResponse<Person> pageResponse = new PageResponse<Person>();
- pageResponse.setTotal(0L);
- pageResponse.setResult(new ArrayList<>());
- return pageResponse;
- }
- // 1:构建查询条件
- Criteria criteria = new Criteria()
- // 条件一:文档中的orderDesc字段要包含查询条件的orderDesc字段
- .and(new Criteria("id").contains(person.getId()))
- // 条件二:文档中的orderNo字段要等于查询条件的orderNo字段
- .and(new Criteria("address").is(person.getAddress()));
- // 封装进入到CriteriaQuery
- CriteriaQuery filter = new CriteriaQuery(criteria);
- // 设置分页信息
- CriteriaQuery criteriaQuery = filter.setPageable(PageRequest.of(pageIndex, pageSize));
- // 3:查询 -> elasticsearchRestTemplate.search(查询条件,返回信息的实体.class)
- SearchHits<Person> searchHits = elasticsearchRestTemplate.search(criteriaQuery, Person.class);
- // 4:结果封装
- // 对每一个都设置对应的高亮字段信息
- List<Person> result = searchHits.get().map(SearchHit::getContent).collect(Collectors.toList());
- // 封装页码信息
- PageResponse<Person> pageResponse = new PageResponse<>();
- pageResponse.setTotal(searchHits.getTotalHits());
- pageResponse.setResult(result);
- return pageResponse;
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |