当前位置:   article > 正文

SpringBoot集成ElasticSearch 7.9.2 教程和简单增删改查案例(基于ElasticsearchRestTemplate)_org.elasticsearch.index.versiontype

org.elasticsearch.index.versiontype

本文只做springboot与es的集成和简单API操作,若想了解es的基础概念和其他知识点,请移步官方网站:开源搜索:Elasticsearch、ELK Stack 和 Kibana 的开发者 | Elastic

推荐一篇es学习的博客:狂神elasticsearch笔记(纯手敲)_哦到凯呀的博客-CSDN博客_狂神说elasticsearch笔记

通用的工具请自取:

SpringBoot基于ElasticSearch7.9.2和ElasticsearchRestTemplate的一些通用接口(可复用)_菜菜的小咸鱼的博客-CSDN博客 

另外,es的更新非常快,目前官网最新的版本已经到7.13了,每个版本的API可能都会有所改动。

本文基于SpringBoot 2.3.1.RELEASE,ES 7.9.2做演示。

开始吧!

首先,肯定要先安装es,kibana,基础的安装步骤、配置、分词器插件配置,这里就不在介绍,百度一搜一大把。

  • 一、pom引入spring-data-elasticsearch
  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  4. </dependency>

注意:不同版本spring-data对应的es的默认配置版本不一样,如:我当前使用的spring-data 版本为4.0,其中默认的es版本为7.6.2,因此我们需要自己指定需要使用的es版本。

在我们项目的pom.xml里面指定es的版本:

  • 二、配置连接参数

1、yml配置文件(主要是为了方便修改),注意这里的缩进,因为没有使用spring自带的es配置,所以节点名称都可以自定义,同时父节点为一级节点。

  1. elasticsearch:
  2. scheme: http
  3. host: localhost
  4. port: 9200
  5. connection-request-timeout: 30000
  6. socket-timeout: 6000000
  7. connect-timeout: 5000000

如果使用spring的es配置,格式如下:

  1. spring:
  2. data:
  3. elasticsearch:
  4. xxxx:zzzz
  5. xxxx:zzzz

 2、新建配置文件读取配置客户端连接。

  1. import org.apache.http.HttpHost;
  2. import org.elasticsearch.client.RestClient;
  3. import org.elasticsearch.client.RestHighLevelClient;
  4. import org.springframework.beans.factory.annotation.Qualifier;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.context.annotation.Configuration;
  8. @Configuration
  9. public class ElasticSearchClientConfig {
  10. @Value("${elasticsearch.host}")
  11. private String host;
  12. @Value("${elasticsearch.port}")
  13. private Integer port;
  14. @Value("${elasticsearch.scheme}")
  15. private String scheme;
  16. @Value("${elasticsearch.connect-timeout}")
  17. private Integer connectTimeout;
  18. @Value("${elasticsearch.socket-timeout}")
  19. private Integer socketTimeout;
  20. @Bean
  21. @Qualifier("highLevelClient")
  22. public RestHighLevelClient restHighLevelClient() {
  23. // 该方法接收一个RequestConfig.Builder对象,对该对象进行修改后然后返回。
  24. RestHighLevelClient highLevelClient = new RestHighLevelClient(
  25. RestClient.builder(new HttpHost(host, port, scheme))
  26. .setRequestConfigCallback(requestConfigBuilder -> {
  27. return requestConfigBuilder.setConnectTimeout(connectTimeout) // 连接超时(默认为1秒)
  28. .setSocketTimeout(socketTimeout);// 套接字超时(默认为30秒)//更改客户端的超时限制默认30秒现在改为100*1000分钟
  29. }));// 调整最大重试超时时间(默认为30秒).setMaxRetryTimeoutMillis(60000);
  30. return highLevelClient;
  31. }
  32. }

至此,springboot配置es就完成了,启动项目即可看到如下信息:因为我们设置了版本和springdata默认的版本一直,所以会警告版本不匹配,不用理会。

  • 三、新建一个实体类,并配置索引映射,这样我们可以直接用这个类去创建索引,并且配置索引字段的mapping

在创建实体类和映射规则前,有必要先了解一下elasticsearch的几个常用注解和枚举:

1、@Document,位于org.springframework.data.elasticsearch.annotations包下。

  1. @Persistent
  2. @Inherited
  3. @Retention(RetentionPolicy.RUNTIME)
  4. @Target({ ElementType.TYPE })
  5. public @interface Document {
  6. /**
  7. * Name of the Elasticsearch index.
  8. * <ul>
  9. * <li>Lowercase only</li>
  10. * <li><Cannot include \, /, *, ?, ", <, >, |, ` ` (space character), ,, #/li>
  11. * <li>Cannot start with -, _, +</li>
  12. * <li>Cannot be . or ..</li>
  13. * <li>Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will
  14. * count towards the 255 limit
  15. * faster)</li>
  16. * </ul>
  17. */
  18. String indexName(); //索引名称,好比MySQL的数据库名
  19. @Deprecated
  20. String type() default ""; //类型 ,当前版本已弃用
  21. /**
  22. * Use server-side settings when creating the index.
  23. * 翻译过来就是:创建索引时使用服务器端设置。
  24. * 这里默认为false,如果改为true,表示在Spring创建索引时,Spring不会在创建的索引中设置以下设
  25. * 置:shards、replicas、refreshInterval和indexStoreType。这些设置将是 Elasticsearch 默认
  26. * 值(服务器配置),也就是说,我们自定义的某些配置将不会生效。
  27. */
  28. boolean useServerConfiguration() default false;
  29. short shards() default 1; // 默认分区数
  30. short replicas() default 1;// 默认每个分区的备份数
  31. String refreshInterval() default "1s"; //索引默认的刷新时间间隔
  32. String indexStoreType() default "fs"; //索引文件存储类型
  33. /**
  34. * Configuration whether to create an index on repository bootstrapping.
  35. */
  36. boolean createIndex() default true; //当spring容器启动时,如果索引不存在,则自动创建索引
  37. VersionType versionType() default VersionType.EXTERNAL;//默认的版本管理类型
  38. }

2、@Field,位于org.springframework.data.elasticsearch.annotations包下:这个注解的字段比较多,这里只列举几个比较常用的

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.FIELD)
  3. @Documented
  4. @Inherited
  5. public @interface Field {
  6. /**
  7. * Alias for {@link #name}.
  8. *
  9. * @since 3.2
  10. */
  11. @AliasFor("name")
  12. String value() default "";
  13. /**
  14. * The <em>name</em> to be used to store the field inside the document.
  15. * <p>
  16. * √5 If not set, the name of the annotated property is used.
  17. *
  18. * @since 3.2
  19. */
  20. @AliasFor("value")
  21. String name() default "";
  22. //上面两个注解,可互为别名使用。
  23. //主要的作用就是指定我们创建索引时,当前字段在索引中的名称,如果不设置,它会默认使用实体类里
  24. // 面使用了@Field这个注解的属性名作为索引字段名。例如:
  25. // class User{
  26. //
  27. // @Field(name = "name" * 或者:value = "name"* )
  28. // private String userName;
  29. // }
  30. // 如上,如果设置了name(或value)值,那么索引里面对应userName的字段名就是“name”,否则就是
  31. // “userName”
  32. FieldType type() default FieldType.Auto; //自动检测索引字段的数据类型,可以根据实际情况自己设置。
  33. DateFormat format() default DateFormat.none;//日期时间格式化,默认不做任何处理
  34. String searchAnalyzer() default "";//检索时的分词策略
  35. String analyzer() default "";//创建索引时指定分词策略
  36. .
  37. .
  38. .
  39. }

3、@Field中提到的FieldType 枚举:

  1. public enum FieldType {
  2. Auto, //根据内容自动判断
  3. Text, //索引全文字段,如产品描述。这些字段会被分词器进行分词处理。
  4. Keyword, //用于索引结构化内容(如电子邮件地址,主机名,状态码,邮政编码等)的字段。他们通常用于过滤、排序、聚合。关键字字段只能根据期确切的值进行搜索。标记为keyword的字段不会被分词器分词处理
  5. Long, //
  6. Integer, //
  7. Short, //
  8. Byte, //
  9. Double, //
  10. Float, //
  11. Half_Float, //
  12. Scaled_Float, //
  13. Date, //
  14. Date_Nanos, //
  15. Boolean, //
  16. Binary, //
  17. Integer_Range, //
  18. Float_Range, //
  19. Long_Range, //
  20. Double_Range, //
  21. Date_Range, //
  22. Ip_Range, //
  23. Object, //
  24. Nested, //
  25. Ip, //可以索引和存储IPV4和IPv6地址
  26. TokenCount, //
  27. Percolator, //
  28. Flattened, //
  29. Search_As_You_Type //
  30. }

好了,了解了基础的注解之后,新建实体类并映射规则,为创建索引做准备:

  1. @Data //lombok的data注解
  2. @Document(indexName = "my_index")
  3. public class EsSourceInfo implements Serializable {
  4. private static final long serialVersionUID = -4780769443664126870L;
  5. @Field(type = FieldType.Keyword) //当前字段不能被分词
  6. private String lngId;
  7. private String remark; //不加@Field注解,创建索引时会使用默认的Field设置
  8. /**
  9. * analyzer = "ik_smart" 创建索引使用的分词策略
  10. * type = FieldType.Text 字段类型为文本类型
  11. * searchAnalyzer = "ik_max_word" 检索时的分词策略
  12. */
  13. @Field(analyzer = "ik_smart",type = FieldType.Text,searchAnalyzer = "ik_max_word")
  14. private String discreption;
  15. /**
  16. * analyzer = "ik_max_word" 创建索引使用的分词策略
  17. * type = FieldType.Text 字段类型为文本类型
  18. * searchAnalyzer = "ik_smart" 检索时的分词策略
  19. */
  20. @Field(analyzer = "ik_max_word",type = FieldType.Text,searchAnalyzer = "ik_smart")
  21. private String address;
  22. /**
  23. * type = FieldType.Keyword字段类型为文本类型
  24. * 这里的keywordsArrays为后期用来做聚类的字段,不会被分词器分词
  25. */
  26. @Field(type = FieldType.Keyword)
  27. private String[] keywordsArrays;
  28. }

上面关于ik分词器的分词策略解释:

 1、ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。

2、ik_smart:会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。

两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用ik_smart

  • 四、索引操作
  1. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
  2. import org.springframework.data.elasticsearch.core.IndexOperations;
  3. import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
  4. import java.lang.annotation.Annotation;
  5. public class IndexOperation {
  6. @Autowired
  7. private ElasticsearchRestTemplate restTemplate;
  8. /**
  9. * @Description 根据实体类创建索引,这里的实体类就是上面创建的EsSourceInfo实体
  10. * @Author Innocence
  11. */
  12. public Boolean createIndexByClass(Class clazz) {
  13. Annotation documentAnnotation = clazz.getDeclaredAnnotation(Document.class);
  14. if(documentAnnotation==null){
  15. return false;
  16. }
  17. String indexName = ((Document) documentAnnotation).indexName();
  18. Boolean indexExist = isIndexExist(indexName);
  19. if (indexExist){
  20. return false;
  21. }
  22. IndexOperations indexOps = restTemplate.indexOps(clazz);
  23. boolean result1 = indexOps.create(); //创建索引
  24. boolean result2 = indexOps.putMapping(indexOps.createMapping(clazz));/设置索引的映射规则,很重要!!!
  25. return result1&result2;
  26. }
  27. /**
  28. * @Description 根据索引名判断索引是否存在
  29. * @Author Innocence
  30. */
  31. public Boolean isIndexExist(String indexName) {
  32. IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of(indexName));
  33. return indexOps.exists();
  34. }
  35. /**
  36. * @Description 根据索引名删除索引
  37. * @Author Innocence
  38. */
  39. public Boolean deleteIndexByName(String indexName) {
  40. IndexOperations indexOps = restTemplate.indexOps(IndexCoordinates.of(indexName));
  41. return indexOps.delete();
  42. }
  43. }
  • 五、文档操作(重点:类似MySQL的CRUD操作)
  1. import com.google.common.collect.Lists;
  2. import org.apache.commons.beanutils.BeanUtils;
  3. import org.apache.commons.beanutils.PropertyUtils;
  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.index.query.QueryBuilders;
  9. import org.elasticsearch.search.aggregations.Aggregation;
  10. import org.elasticsearch.search.aggregations.Aggregations;
  11. import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
  12. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  13. import org.elasticsearch.search.builder.SearchSourceBuilder;
  14. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.data.domain.Page;
  17. import org.springframework.data.domain.PageImpl;
  18. import org.springframework.data.domain.PageRequest;
  19. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
  20. import org.springframework.data.elasticsearch.core.SearchHit;
  21. import org.springframework.data.elasticsearch.core.SearchHits;
  22. import org.springframework.data.elasticsearch.core.document.Document;
  23. import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
  24. import org.springframework.data.elasticsearch.core.query.*;
  25. import java.io.IOException;
  26. import java.lang.reflect.InvocationTargetException;
  27. import java.util.ArrayList;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. public class DocumentOperation {
  32. @Autowired
  33. private ElasticsearchRestTemplate restTemplate;
  34. @Autowired
  35. private RestHighLevelClient highLevelClient;
  36. /**
  37. * 映射高亮字段到原生属性,es的高亮查询不会直接把高亮字段映射到实体类,所以这里我们要自己处理
  38. * @author Innocence
  39. * @param searchHits
  40. * @return java.util.List<T>
  41. */
  42. private <T> List<T> mappingHighlight(List<SearchHit<T>> searchHits){
  43. List<T> infoList = Lists.newArrayList();
  44. for (SearchHit<T> searchHit : searchHits) {
  45. T content = searchHit.getContent();
  46. Map<String, List<String>> highlightFields = searchHit.getHighlightFields();
  47. for (Map.Entry<String, List<String>> entry : highlightFields.entrySet()) {
  48. try {
  49. PropertyUtils.setProperty(content,entry.getKey(),entry.getValue().get(0));
  50. } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. infoList.add(content);
  55. }
  56. return infoList;
  57. }
  58. /**
  59. * 设置高亮映射规则
  60. * @author Innocence
  61. * @date 2021/6/9
  62. * @param [fields]
  63. * @return org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
  64. */
  65. public HighlightBuilder getHighlightBuilder(String[] fields) {
  66. HighlightBuilder highlightBuilder = new HighlightBuilder();
  67. for (String field : fields) {
  68. highlightBuilder.field(field);
  69. }
  70. highlightBuilder.requireFieldMatch(false); //如果要多个字段高亮,这项要为false
  71. highlightBuilder.preTags("<span style=\"color:red\">");
  72. highlightBuilder.postTags("</span>");
  73. //下面这两项,如果你要高亮如文字内容等有很多字的字段,必须配置,不然会导致高亮不全,文章内容缺失等
  74. highlightBuilder.fragmentSize(800000); //最大高亮分片数
  75. highlightBuilder.numOfFragments(0); //从第一个分片获取高亮片段
  76. return highlightBuilder;
  77. }
  78. /**
  79. * 根据id判断文档是否存在于指定索引中
  80. * @author Innocence
  81. * @param id 文档id,这里的id是需要我们在存储数据时指定
  82. * @param indexName 索引名
  83. * @return java.lang.Boolean
  84. */
  85. public Boolean isExist(String id,String indexName) {
  86. return restTemplate.exists(id, IndexCoordinates.of(indexName));
  87. }
  88. /**
  89. * 单条数据插入
  90. * @author Innocence
  91. * @param entity 待插入的数据实体
  92. * @param indexName 索引名
  93. * @return java.lang.String 返回文档id
  94. */
  95. public String saveByEntity(EsSourceInfo entity, String indexName) throws Exception{
  96. if (StrUtil.isBlank(entity.getLngId())){
  97. throw new Exception("文档id不能为空!");
  98. }
  99. IndexQuery build = new IndexQueryBuilder()
  100. .withId(entity.getLngId()) //这里的操作就是指定文档id
  101. .withObject(entity).build();
  102. return restTemplate.index(build,IndexCoordinates.of(indexName));
  103. }
  104. /**
  105. * 批量插入
  106. * @author Innocence
  107. * @param entities 待插入的数据实体集合
  108. * @param indexName 索引名
  109. * @return java.util.List<java.lang.String> 返回idList
  110. */
  111. public List<String> saveBatchByEntities(List<EsSourceInfo> entities, String indexName) throws Exception{
  112. List<IndexQuery> queryList = new ArrayList<>();
  113. for (EsSourceInfo item:entities){
  114. if (StrUtil.isBlank(item.getLngId())){
  115. throw new Exception("文档id不能为空!");
  116. }
  117. IndexQuery build = new IndexQueryBuilder().withId(item.getLngId()).withObject(item).build();
  118. queryList.add(build);
  119. }
  120. return restTemplate.bulkIndex(queryList, IndexCoordinates.of(indexName));
  121. }
  122. /**
  123. * 单条数据更新
  124. * @author Innocence
  125. * @param entity 待更新的数据实体
  126. * @param indexName 索引名
  127. * @return void
  128. */
  129. public void updateByEntity(EsSourceInfo entity, String indexName) {
  130. Map<String,Object> map = null;
  131. try {
  132. map = BeanUtils.describe(entity);
  133. } catch (Exception e) {
  134. e.printStackTrace();
  135. }
  136. Document document = Document.from(map);
  137. document.setId(entity.getLngId());
  138. // 这里的UpdateQuery需要构造一个Document对象,但是Document对象不能用实体类转化而来
  139. //(可见Document的源码,位于:org.springframework.data.elasticsearch.core.document
  140. // 包下),因此上面才会BeanUtils.describe(entity),将实体类转化成一个map,由map转化
  141. // 为Document对象。
  142. UpdateQuery build = UpdateQuery.builder(entity.getLngId())
  143. .withDocAsUpsert(false) //不加默认false。true表示更新时不存在就插入
  144. .withDocument(document)
  145. .build();
  146. restTemplate.update(build, IndexCoordinates.of(indexName));
  147. }
  148. /**
  149. * 根据maps批量更新
  150. * @author Innocence
  151. * @param maps 待更新的数据实体集合
  152. * @param indexName 索引名
  153. * @return void
  154. */
  155. public void updateByMaps(List<Map<String, Object>> maps, String indexName) {
  156. List<UpdateQuery> updateQueries = new ArrayList<>();
  157. maps.forEach(item->{
  158. Document document = Document.from(item);
  159. document.setId(String.valueOf(item.get("lngid")));
  160. UpdateQuery build = UpdateQuery.builder(document.getId())
  161. .withDocument(document)
  162. .build();
  163. updateQueries.add(build);
  164. });
  165. restTemplate.bulkUpdate(updateQueries,IndexCoordinates.of(indexName));
  166. }
  167. /**
  168. * 根据id删除数据
  169. * @author Innocence
  170. * @param id
  171. * @param indexName 索引名
  172. * @return java.lang.String 被删除的id
  173. */
  174. public String deleteById(String id, String indexName) {
  175. return restTemplate.delete(id,IndexCoordinates.of(indexName));
  176. }
  177. /**
  178. * 根据id批量删除数据
  179. * @author Innocence
  180. * @param docIdName 文档id字段名,如我们上面设置的文档id的字段名为“lngId”
  181. * @param ids 需要删除的id集合
  182. * @param indexName 索引名称
  183. * @return void
  184. */
  185. public void deleteByIds(String docIdName , List<String> ids, String indexName) {
  186. StringQuery query = new StringQuery(QueryBuilders.termsQuery(docIdName, ids).toString());
  187. restTemplate.delete(query,null,IndexCoordinates.of(indexName));
  188. }
  189. /**
  190. * 根据条件删除数据
  191. * @author Innocence
  192. * @param query 条件构造器
  193. * @param clazz 数据对应实体类
  194. * @param indexName 索引名
  195. * @return void
  196. */
  197. public void deleteByQuery(Query query, Class<?> clazz, String indexName) {
  198. restTemplate.delete(query,clazz,IndexCoordinates.of(indexName));
  199. }
  200. /**
  201. * 根据id查询数据 (基于注解形式设置了索引mapping)
  202. * @param id
  203. */
  204. public EsSourceInfo getEntityById(String id) {
  205. return restTemplate.get(id,EsSourceInfo.class);
  206. }
  207. /**
  208. * 查询符合条件的总条数
  209. * @author Innocence
  210. * @return java.lang.Long
  211. */
  212. public Long getCount(Query query, Class clazz) {
  213. return restTemplate.count(query,clazz);
  214. }
  215. /**
  216. * 查询符合条件的实体list
  217. * @author Innocence
  218. * @param query 构建的查询条件 主要使用NativeSearchQuery 来进行构造
  219. * @return java.util.List<com.cqvip.innocence.project.model.entity.EsSourceInfo>
  220. */
  221. public List<EsSourceInfo> getInfoList(Query query, String indexName) {
  222. // es本身默认限制了查找的量为10000条,官方文档的建议是不要修改,太大会影响性能和效率,
  223. // 建议使用 scroll 来代替。如果超出10000条,返回的结果最多只有10000条
  224. // 所以,我们在这里设置setTrackTotalHits(true),返回真实的命中条数
  225. query.setTrackTotalHits(true);
  226. SearchHits<EsSourceInfo> search = restTemplate.search(query, EsSourceInfo.class, IndexCoordinates.of(indexName));
  227. return mappingHighlight(search.getSearchHits());//映射的高亮字段
  228. }
  229. /**
  230. * 分页查询
  231. * @author Innocence
  232. * @param query 构建的查询条件 主要使用NativeSearchQuery 来进行构造
  233. * @return org.springframework.data.domain.Page<com.cqvip.innocence.project.model.entity.EsSourceInfo>
  234. */
  235. public Map<String, Object> getPageList(Query query, PageRequest pageRequest) {
  236. query.setTrackTotalHits(true);
  237. SearchHits<EsSourceInfo> search = restTemplate.search(query, EsSourceInfo.class);
  238. Aggregations aggregations = search.getAggregations();
  239. List<SearchHit<EsSourceInfo>> searchHits = search.getSearchHits();
  240. List<EsSourceInfo> esSourceInfos = mappingHighlight(searchHits);
  241. Page infos = new PageImpl(
  242. esSourceInfos,
  243. pageRequest,
  244. search.getTotalHits());
  245. Map<String, Object> map = new HashMap<>();
  246. map.put("page",infos);
  247. map.put("ag",formatFacet(aggregations));
  248. return map;
  249. }
  250. /**
  251. * 根据条件获取聚类信息
  252. * springboot-data 封装的聚类查询速度很慢,这里直接用client操作
  253. * @author Innocence
  254. * @date 2021/3/19
  255. * @param query, indexName]
  256. * @return java.util.Map<java.lang.String,java.util.List<? extends org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket>>
  257. */
  258. public Map<String, List<? extends Terms.Bucket>> getFacetByQuery(SearchSourceBuilder query, String indexName) throws IOException {
  259. SearchRequest request = new SearchRequest(indexName);
  260. SearchSourceBuilder builder = query;
  261. request.source(builder);
  262. SearchResponse response = highLevelClient.search(request, RequestOptions.DEFAULT);
  263. Aggregations aggregations = response.getAggregations();
  264. return formatFacet(aggregations);
  265. }
  266. /**
  267. * 格式化聚类数据
  268. * @author Innocence
  269. * @param aggregations
  270. * @return java.util.Map<java.lang.String,java.util.List<java.util.Map<java.lang.String,java.lang.Object>>>
  271. */
  272. private Map<String, List<? extends Terms.Bucket>> formatFacet(Aggregations aggregations){
  273. if (aggregations == null){
  274. return null;
  275. }
  276. Map<String, List<? extends Terms.Bucket>> map = new HashMap<>();
  277. List<Aggregation> list = aggregations.asList();
  278. list.forEach(item->{
  279. ParsedStringTerms newItem = (ParsedStringTerms) item;
  280. String name = newItem.getName();
  281. List<? extends Terms.Bucket> buckets = newItem.getBuckets();
  282. map.put(name,buckets);
  283. });
  284. return map;
  285. }
  286. }
  • 六、条件构造案例

上面的文档操作中,提到很多个Query条件构造器,其中IndexQuery和UpdateQuery上面的操作工具类里面就有构造方式和使用方法,主要还是关于查询的Query,很多同学可能会一脸蒙蔽,这里放一个我项目中使用的真实案列,具体的细节还要多看文档。

  1. package com.cqvip.innocence.project.controller.front.search;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.cqvip.innocence.common.annotation.SensitiveTag;
  4. import com.cqvip.innocence.common.constant.EsIndexConstant;
  5. import com.cqvip.innocence.project.esservice.DocumentService;
  6. import com.cqvip.innocence.project.model.dto.JsonResult;
  7. import com.cqvip.innocence.project.model.dto.SearchModel;
  8. import com.cqvip.innocence.project.model.dto.SearchParams;
  9. import com.cqvip.innocence.project.model.enums.ResourceType;
  10. import com.cqvip.innocence.project.model.enums.SearchFiled;
  11. import io.swagger.annotations.ApiOperation;
  12. import org.elasticsearch.index.query.*;
  13. import org.elasticsearch.search.aggregations.AggregationBuilders;
  14. import org.elasticsearch.search.aggregations.BucketOrder;
  15. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  16. import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
  17. import org.elasticsearch.search.builder.SearchSourceBuilder;
  18. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  19. import org.elasticsearch.search.sort.SortBuilder;
  20. import org.elasticsearch.search.sort.SortBuilders;
  21. import org.elasticsearch.search.sort.SortOrder;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.data.domain.PageRequest;
  24. import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
  25. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  26. import org.springframework.web.bind.annotation.*;
  27. import javax.servlet.http.HttpServletRequest;
  28. import java.io.IOException;
  29. import java.util.*;
  30. import java.util.stream.Collectors;
  31. /**
  32. * @ClassName CommonSearchController
  33. * @Description 前台通用的检索接口
  34. * @Author Innocence
  35. * @Date 2021/3/18 9:26
  36. * @Version 1.0
  37. */
  38. @RestController
  39. @RequestMapping("/search/")
  40. public class CommonSearchController {
  41. @Autowired
  42. private DocumentService documentService;
  43. @GetMapping("getArticleFacetsToMedia")
  44. @ApiOperation("期刊详情页获取文献年期聚类")
  45. public JsonResult getArticleFacets(String gch) throws IOException {
  46. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  47. boolQueryBuilder.must(QueryBuilders.matchQuery("workType",ResourceType.ARTICLE.getCode()))
  48. .must(QueryBuilders.termQuery("gch",gch));
  49. TermsAggregationBuilder yearsFacet = AggregationBuilders
  50. .terms("years")
  51. .field("years")
  52. .order(BucketOrder.key(false))
  53. .size(150);
  54. yearsFacet.subAggregation(AggregationBuilders
  55. .terms("num")
  56. .field("num")
  57. .order(BucketOrder.key(true))
  58. .size(150));
  59. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  60. searchSourceBuilder.query(boolQueryBuilder).aggregation(yearsFacet);
  61. Map<String, List<? extends Terms.Bucket>> facetByQuery = documentService.getFacetByQuery(searchSourceBuilder, EsIndexConstant.INDEX_NAME);
  62. return JsonResult.Get().putRes(facetByQuery);
  63. }
  64. @SensitiveTag
  65. @PostMapping("advanceSearch")
  66. @ApiOperation("所有资源分页检索")
  67. public JsonResult getAllSourcePage(@RequestBody SearchParams params,HttpServletRequest request){
  68. Integer current;
  69. //设置分页
  70. if (params.getPageNum() == null){
  71. current = 0;
  72. }else {
  73. current = params.getPageNum()-1;
  74. }
  75. if (params.getPageSize() == null){
  76. params.setPageSize(20);
  77. }
  78. PageRequest pageRequest = PageRequest.of(current,params.getPageSize());
  79. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
  80. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  81. //排序
  82. if (params.getSortMap()!= null){
  83. nativeSearchQueryBuilder = setSort(params.getSortMap());
  84. }
  85. // 聚类
  86. List<String> list = new ArrayList<>();
  87. list.add("workType");
  88. list.add("classTypesArrays");
  89. list.add("keywordsArrays");
  90. list.add("writersArrays");
  91. List<TermsAggregationBuilder> termsAggregationBuilders = addAggregation(list);
  92. for (TermsAggregationBuilder item:termsAggregationBuilders) {
  93. nativeSearchQueryBuilder.addAggregation(item);
  94. }
  95. setParams(params,boolQueryBuilder,nativeSearchQueryBuilder);
  96. NativeSearchQuery build = nativeSearchQueryBuilder
  97. .withQuery(boolQueryBuilder)
  98. .withPageable(pageRequest)
  99. .build();
  100. Map<String, Object> pageList = documentService.getPageList(build, pageRequest);
  101. return JsonResult.Get().putRes(pageList);
  102. }
  103. /**
  104. * 所有资源查询的时候组装条件
  105. * @author Innocence
  106. * @date 2021/3/23
  107. * @param params
  108. * @param boolQueryBuilder
  109. * @return void
  110. */
  111. @SensitiveTag
  112. private void setParams(SearchParams params,BoolQueryBuilder boolQueryBuilder,NativeSearchQueryBuilder nativeSearchQueryBuilder){
  113. List<String> highList = new ArrayList<>();
  114. List<String> classTypes = params.getClassTypes();
  115. //学科分类检索
  116. if (classTypes != null && classTypes.size()>0){
  117. TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("classTypesArrays.keyword", classTypes);
  118. boolQueryBuilder.must(termsQueryBuilder);
  119. }
  120. //1、简单检索(可能什么值都不传,那就查询全部数据)
  121. if (params.getSimpleSearchParams() != null && StrUtil.isNotBlank(params.getSimpleSearchParams().searchKeyword)){
  122. List<String> fields = getField(params.getSimpleSearchParams().getSearchField());
  123. fields.forEach(item->highList.add(item));
  124. DisMaxQueryBuilder queryBuilder = QueryBuilders.disMaxQuery().tieBreaker(0.05f);
  125. Boolean isExact = params.getSimpleSearchParams().getIsExact();
  126. fields.forEach(item->{
  127. if (item.contains("title") || item.contains("media") || item.contains("book")){
  128. MatchQueryBuilder boost = QueryBuilders
  129. .matchQuery(item, params.getSimpleSearchParams().searchKeyword).boost(2f);
  130. queryBuilder.add(boost);
  131. }else{
  132. if (isExact != null && isExact == true){
  133. QueryBuilder matchPhraseQueryBuilder = QueryBuilders.termQuery(item, params.getSimpleSearchParams().searchKeyword);
  134. queryBuilder.add(matchPhraseQueryBuilder);
  135. }else {
  136. QueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery(item, params.getSimpleSearchParams().searchKeyword);
  137. queryBuilder.add(matchPhraseQueryBuilder);
  138. }
  139. }
  140. });
  141. boolQueryBuilder.must(queryBuilder);
  142. }
  143. //2、二次检索
  144. if (params.getSecondSearchParams() != null && params.getSecondSearchParams().size()>0){
  145. List<SearchModel> models = params.getSecondSearchParams();
  146. models.forEach(item->{
  147. List<String> fields = getField(item.getSearchField());
  148. fields.forEach(h->highList.add(h));
  149. DisMaxQueryBuilder queryBuilder = QueryBuilders.disMaxQuery().tieBreaker(0.05f);
  150. fields.forEach(e->{
  151. if (e.contains("title") || e.contains("media") || e.contains("book")){
  152. MatchPhraseQueryBuilder boost = QueryBuilders
  153. .matchPhraseQuery(e, item.getSearchKeyword()).boost(2f);
  154. queryBuilder.add(boost);
  155. }else{
  156. MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders
  157. .matchPhraseQuery(e, item.getSearchKeyword());
  158. queryBuilder.add(matchPhraseQueryBuilder);
  159. }
  160. });
  161. boolQueryBuilder.must(queryBuilder);
  162. });
  163. }
  164. //3、聚类检索
  165. if (params.getFacetSearchParams() != null && params.getFacetSearchParams().size() > 0) {
  166. params.getFacetSearchParams().forEach((item) -> {
  167. List<String> fields = getField(item.getSearchField());
  168. fields.forEach(h->highList.add(h));
  169. DisMaxQueryBuilder queryBuilder = QueryBuilders.disMaxQuery().tieBreaker(0.05f);
  170. fields.forEach(e -> {
  171. if(e.equals(SearchFiled.WORKTYPE.getValue())){
  172. MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(e, item.getSearchKeyword());
  173. queryBuilder.add(matchQueryBuilder);
  174. }else {
  175. MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery(e, item.getSearchKeyword());
  176. queryBuilder.add(matchPhraseQueryBuilder);
  177. }
  178. });
  179. boolQueryBuilder.must(queryBuilder);
  180. });
  181. }
  182. //4、高级检索
  183. if (params.getAdvanceSearchParams() != null && params.getAdvanceSearchParams().size() > 0) {
  184. BoolQueryBuilder bool=new BoolQueryBuilder();
  185. params.getAdvanceSearchParams().forEach((item) -> {
  186. List<String> fields = getField( item.getSearchField());
  187. fields.forEach(h->highList.add(h));
  188. //精确
  189. BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
  190. BoolQueryBuilder child=new BoolQueryBuilder();
  191. if (item.getIsExact()) {
  192. fields.forEach(field-> child.should(QueryBuilders.termQuery(field,item.getSearchKeyword())));
  193. queryBuilder.must(child);
  194. }else {
  195. MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery(
  196. item.getSearchKeyword(), fields.toArray(new String[fields.size()]));
  197. queryBuilder.should(multiMatchQueryBuilder);
  198. }
  199. if (StrUtil.equals("AND", item.getLogicOperator().trim())) {
  200. bool.must(queryBuilder);
  201. } else if (StrUtil.equals("OR", item.getLogicOperator().trim())) {
  202. bool.should(queryBuilder);
  203. } else if (StrUtil.equals("NOT", item.getLogicOperator().trim())) {
  204. bool.mustNot(queryBuilder);
  205. }
  206. });
  207. boolQueryBuilder.must(bool);
  208. }
  209. List<String> highFields = filterHighFields(highList);
  210. nativeSearchQueryBuilder.withHighlightBuilder(setHighlight(highFields));
  211. }
  212. /**
  213. * 根据list设置高亮字段
  214. * @author Innocence
  215. * @date 2021/3/23
  216. * @param list
  217. * @return org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder
  218. */
  219. private HighlightBuilder setHighlight(List<String> list){
  220. List<String> newList = new ArrayList<>();
  221. for (int i = 0; i < list.size(); i++) {
  222. if (list.get(i).indexOf(",") != -1){
  223. String[] split = list.get(i).split(",");
  224. for (int j = 0; j < split.length; j++) {
  225. newList.add(split[j]);
  226. }
  227. }
  228. newList.add(list.get(i));
  229. }
  230. String[] strings = newList.toArray(new String[newList.size()]);
  231. HighlightBuilder highlightBuilder = documentService.getHighlightBuilder(strings);
  232. return highlightBuilder;
  233. }
  234. /**
  235. * 根据检索对象和缩略词获取字段
  236. * @param filed 检索字段
  237. * @return {@link String} 形如 title,title_c
  238. * @author 01
  239. * @date 2021/1/19 18:40
  240. */
  241. private List<String> getField( SearchFiled filed) {
  242. List<String> fields = new ArrayList<>();
  243. //任意字段
  244. if (StrUtil.equals(filed.getValue(), SearchFiled.ALL.getValue())) {
  245. SearchFiled[] values = SearchFiled.values();
  246. for (SearchFiled value : values) {
  247. if (StrUtil.equals(value.getValue(), SearchFiled.ALL.getValue())
  248. || StrUtil.equals(value.getValue(), SearchFiled.GCH.getValue())
  249. || StrUtil.equals(value.getValue(), SearchFiled.PUBLISHER.getValue())) {
  250. continue;
  251. }
  252. fields.add(value.getValue());
  253. }
  254. //标题
  255. }else if (StrUtil.equals(filed.getValue(),SearchFiled.TITLE.getValue())){
  256. String titleValue = SearchFiled.TITLE.getValue();
  257. for (String value : titleValue.split(",")) {
  258. fields.add(value);
  259. }
  260. } else {
  261. fields.add(filed.getValue());//单独字段
  262. }
  263. List<String> strings = new ArrayList<>();
  264. fields.forEach(item ->{
  265. if (item.indexOf(",") !=-1){
  266. String[] split = item.split(",");
  267. for (int i = 0; i < split.length; i++) {
  268. strings.add(split[i]);
  269. }
  270. }else {
  271. strings.add(item);
  272. }
  273. });
  274. return strings;
  275. }
  276. /**
  277. * 组装排序字段
  278. * @author Innocence
  279. * @date 2021/3/18
  280. * @param map
  281. * @return java.util.List<org.elasticsearch.search.sort.SortBuilder>
  282. */
  283. private NativeSearchQueryBuilder setSort(Map<String, SortOrder> map){
  284. NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
  285. for(Map.Entry<String, SortOrder> entry : map.entrySet()){
  286. SortBuilder order;
  287. if (entry.getKey().startsWith("_")){
  288. if (entry.getValue().equals(SortOrder.ASC)){
  289. order = SortBuilders.fieldSort(entry.getKey()).order(SortOrder.ASC);
  290. }else {
  291. order = SortBuilders.fieldSort(entry.getKey()).order(SortOrder.DESC);
  292. }
  293. }else {
  294. if (entry.getValue().equals(SortOrder.ASC)){
  295. order = SortBuilders.fieldSort(entry.getKey()+".keyword").order(SortOrder.ASC);
  296. }else {
  297. order = SortBuilders.fieldSort(entry.getKey()+".keyword").order(SortOrder.DESC);
  298. }
  299. }
  300. nativeSearchQueryBuilder.withSort(order);
  301. }
  302. return nativeSearchQueryBuilder;
  303. }
  304. /**
  305. * 组装聚类的字段
  306. * @author Innocence
  307. * @date 2021/3/18
  308. * @param fields 需要聚类的字段
  309. * @return java.util.List<org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder>
  310. */
  311. private List<TermsAggregationBuilder> addAggregation(List<String> fields){
  312. List<TermsAggregationBuilder> builders = new ArrayList<>();
  313. fields.forEach(item->{
  314. TermsAggregationBuilder size;
  315. if (item.equals("workType")){
  316. size = AggregationBuilders.terms(item).field(item+".keyword").size(150);
  317. }else {
  318. size = AggregationBuilders.terms(item).field(item).size(150);
  319. }
  320. builders.add(size);
  321. });
  322. return builders;
  323. }
  324. /**
  325. * 高亮字段不能为数组字段,这里要处理一下
  326. * @author Innocence
  327. * @date 2021/3/24
  328. * @return void
  329. */
  330. private List<String> filterHighFields(List<String> fields){
  331. List<String> myList = fields.stream().distinct().collect(Collectors.toList());
  332. List<String> strings = new ArrayList<>();
  333. for (int i = 0; i < myList.size(); i++) {
  334. if (myList.get(i).equals(SearchFiled.CLASS_TYPES_ARRAYS.getValue())){
  335. strings.add(SearchFiled.CLASS_TYPES.getValue());
  336. }else if(myList.get(i).equals(SearchFiled.KEYWORDS_ARRAYS.getValue())){
  337. strings.add(SearchFiled.KEYWORD.getValue());
  338. }else if (myList.get(i).equals(SearchFiled.WRITERS_ARRAYS.getValue())){
  339. strings.add(SearchFiled.WRITER.getValue());
  340. }else if(myList.get(i).equals(SearchFiled.COLLECT_DATABASE_ARRAYS.getValue())) {
  341. strings.add(SearchFiled.COLLECT_DATABASE.getValue());
  342. }else if(myList.get(i).equals("workType")){
  343. continue;
  344. }else {
  345. strings.add(myList.get(i));
  346. }
  347. }
  348. return strings.stream().distinct().collect(Collectors.toList());
  349. }
  350. }

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

闽ICP备14008679号