上文提到了SpringBoot如何集成 ES JPA和其基本使用,接下来我们来看看更底层的类:ElasticsearchRestTemplate的使用。它其实是一个更具有灵活性的基于 ES HighLevelAPI 的 CRUD模板库,使用起来也非常方便。

增删改

通过新增 2 条数据进行操作

  1. @Test
  2. public void save1() {
  3. IndexCoordinates index = IndexCoordinates.of("art");
  4. Article mock = new Article();
  5. mock.setId(1L);
  6. mock.setAuthor("王天黑");
  7. mock.setTitle("发现Kotlin一个神奇的bug");
  8. mock.setBody("本文将会通过具体的业务场景,由浅入深的引出Kotlin的一个bug,并告知大家这个bug的神奇之处,接着会带领大家去查找bug出现的原因,最后去规避这个bug。");
  9. mock.setTimes(568);
  10. mock.setPublishDate(LocalDate.of(2021, 1, 27));
  11. mock.setImages("https://juejin.cn/post/6921359126438084621");
  12. Article art = esRestTemplate.save(mock, index);
  13. System.out.println(art);
  14. //第二个
  15. Article article2 = new Article();
  16. article2.setId(2L);
  17. article2.setAuthor("刘禹锡");
  18. article2.setTitle("自从上了K8S,项目更新都不带停机的!");
  19. article2.setBody("如果你看了《Kubernetes太火了!花10分钟玩转它不香么?》一文的话,基本上已经可以玩转K8S了。其实K8S中还有一些高级特性也很值得学习,比如弹性扩缩应用、滚动更新、配置管理、存储卷、网关路由等。今天我们就来了解下这些高级特性,希望对大家有所帮助!");
  20. article2.setTimes(120);
  21. article2.setPublishDate(LocalDate.of(2020, 11, 27));
  22. article2.setImages("https://juejin.cn/post/6922236829278306311");
  23. esRestTemplate.save(article2, index);
  24. System.out.println("over...");
  25. }

通过日志可以看出对应的请求是

  1. curl -iX GET 'http://my.search.com:9200/'
  2. curl -iX HEAD 'http://my.search.com:9200/art?ignore_throttled=false&ignore_unavailable=false&expand_wildcards=open%2Cclosed&allow_no_indices=false'
  3. curl -iX PUT 'http://my.search.com:9200/art?master_timeout=30s&timeout=30s' -d '{"settings":{"index":{"analysis":{"filter":{"my_pinyin":{"keep_joined_full_pinyin":"true","lowercase":"true","none_chinese_pinyin_tokenize":"false","keep_original":"true","keep_none_chinese_together":"true","keep_first_letter":"true","trim_whitespace":"true","type":"pinyin","keep_none_chinese":"true","keep_full_pinyin":"false"}},"char_filter":{"ue_char_filter":{"type":"mapping","mappings":["- => ,","— => ,"]}},"analyzer":{"ue_ik_pinyin_analyzer":{"filter":["my_pinyin"],"char_filter":["html_strip","ue_char_filter"],"type":"custom","tokenizer":"ik_max_word"},"ue-ngram":{"type":"custom","char_filter":["html_strip","ue_char_filter"],"tokenizer":"ngram_tokenizer"}},"tokenizer":{"ngram_tokenizer":{"token_chars":["letter","digit"],"min_gram":"2","type":"ngram","max_gram":"3"}}},"number_of_replicas":"1"}},"aliases":{}}'
  4. curl -iX PUT 'http://my.search.com:9200/art/_mapping?master_timeout=30s&ignore_unavailable=false&expand_wildcards=open%2Cclosed&allow_no_indices=false&ignore_throttled=false&timeout=30s' -d '{"properties":{"id":{"type":"keyword","index":true},"author":{"type":"text","analyzer":"ue-ngram","search_analyzer":"ue-ngram"},"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},"body":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},"times":{"type":"long"},"images":{"type":"keyword","index":false},"publishDate":{"type":"date","format":"basic_date"}}}'
  5. curl -iX HEAD 'http://my.search.com:9200/book?ignore_throttled=false&ignore_unavailable=false&expand_wildcards=open%2Cclosed&allow_no_indices=false'
  6. curl -iX PUT 'http://my.search.com:9200/art/_doc/1?timeout=1m' -d '{"_class":"club.hicode.dockerhi.entity.Article","id":1,"author":"王天黑","title":"发现Kotlin一个神奇的bug","body":"本文将会通过具体的业务场景,由浅入深的引出Kotlin的一个bug,并告知大家这个bug的神奇之处,接着会带领大家去查找bug出现的原因,最后去规避这个bug。","times":568,"images":"https://juejin.cn/post/6921359126438084621","publishDate":"20210127"}'
  7. curl -iX PUT 'http://my.search.com:9200/art/_doc/2?timeout=1m' -d '{"_class":"club.hicode.dockerhi.entity.Article","id":2,"author":"刘禹锡","title":"自从上了K8S,项目更新都不带停机的!","body":"如果你看了《Kubernetes太火了!花10分钟玩转它不香么?》一文的话,基本上已经可以玩转K8S了。其实K8S中还有一些高级特性也很值得学习,比如弹性扩缩应用、滚动更新、配置管理、存储卷、网关路由等。今天我们就来了解下这些高级特性,希望对大家有所帮助!","times":120,"images":"https://juejin.cn/post/6922236829278306311","publishDate":"20201127"}'

分析后可以得出,保存的时候,进行了如下操作

  1. 检测ES服务是否正常
  2. 检测该index 是否存在
  3. 如果不存在,那么创建 index
  4. 最后插入数据

由此可以得出结论:ElasticsearchRestTemplate也是会自动创建索引的。

查询

下面给出一个最通用的 构建查询条件的示例

  1. NativeSearchQuery build = new NativeSearchQueryBuilder()
  2. // 构建查询条件
  3. .withQuery(queryBuilder(obj))
  4. //构建过滤条件:不参与算法
  5. .withFilter(filterBuilder(obj))
  6. //排序
  7. .withSort(sortBuilder())
  8. //分页
  9. .withPageable(PageRequest.of((int) obj.getCurrent() - 1, (int) obj.getSize()))
  10. //高亮构建
  11. .withHighlightBuilder(builderHighlight())
  12. //高亮字段
  13. .withHighlightFields(builderHighlightFields()).build();

通过一个案例进行一次综合性讲解,实体类Article

  1. @Setting(settingPath = "es/es-setting.json")
  2. @Document(indexName = "art")
  3. public class Article {
  4. @Id
  5. @Field(type = FieldType.Long)
  6. private Long id;
  7. @Field(type = FieldType.Text, searchAnalyzer = "ue-ngram", analyzer = "ue-ngram")
  8. private String author;
  9. @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
  10. private String title;
  11. @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
  12. private String body;
  13. @Field(type = FieldType.Keyword, index = false)
  14. private String images;
  15. @Field(type = FieldType.Date, format = DateFormat.date)
  16. private LocalDate publishDate;
  17. @Field(type = FieldType.Integer)
  18. private Integer asDelete;
  19. @Field(type = FieldType.Long)
  20. private Long readTimes;
  21. //...省略...//
  22. }

上面是一个文章的数据结构,现在要实现一个需求:

查询出:阅读次数不小于 100 次的,且发布时间在 2020年10 月 1 日之后的,标题中带有 kotlin 或者作者是刘禹锡。

对应的 SQL 简化如下

  1. where postDate > '2020-10-01' and (title like '%kotlin%' or author like '%禹锡%') and readTimes>= 100

那么这个查询如何写了

  1. /**
  2. * SQL:
  3. * WHERE postDate > '2020-10-01' AND (title LIKE '%kotlin%' OR author LIKE '%禹锡%') AND readTimes>= 100
  4. */
  5. @Test
  6. public void testFind() {
  7. //0.构建 index
  8. IndexCoordinates index = IndexCoordinates.of("art");
  9. //1.1 通过 QueryBuilders 构建查询条件。将 title 的权重提高到 3
  10. MatchQueryBuilder titleQuery = QueryBuilders.matchQuery("title", "kotlin").boost(3.0f);
  11. MatchQueryBuilder authorQuery = QueryBuilders.matchQuery("author", "刘禹锡");
  12. BoolQueryBuilder complexQuery = QueryBuilders.boolQuery().should(titleQuery).should(authorQuery);
  13. //2.1 构建时间 Query。理论上应该作为 filter存在,此处为了综合 bool 查询,做此处理。
  14. RangeQueryBuilder dateQuery = QueryBuilders.rangeQuery("publishDate").gt("2020-10-01");
  15. //3.合并之后的 Query
  16. BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery().must(dateQuery).must(complexQuery);
  17. //4.构建 FilterBuilder,此处将阅读次数作为 filter 条件,提高效率,该字段不参与计分。理论上大于小于字段都应该作为 filter 存在
  18. RangeQueryBuilder readTimesQuery = QueryBuilders.rangeQuery("readTimes").gte(100);
  19. //5.设置高亮字段
  20. HighlightBuilder.Field[] highBuilder = new HighlightBuilder.Field[]{
  21. new HighlightBuilder.Field("title"), //title 高亮
  22. new HighlightBuilder.Field("author") //author 高亮
  23. };
  24. //6.构建分页
  25. PageRequest page = PageRequest.of(0, 5);
  26. //7.构建条件
  27. NativeSearchQuery query = new NativeSearchQueryBuilder()
  28. .withQuery(queryBuilder)
  29. .withFilter(readTimesQuery)
  30. .withPageable(page)
  31. .withHighlightFields(highBuilder)
  32. .build();
  33. //8.执行查询
  34. SearchHits<Article> result = esRestTemplate.search(query, Article.class, index);
  35. //9.打印结果
  36. result.getSearchHits().forEach(System.out::println);
  37. }

上述的代码注释已经比较完善了,可以自行理解。

关键难点:QueryBuilders的使用构建复杂的查询条件,后续可以自行根据上述例子进行摸索和查看。如果你的 ES基础不算差,那么很容易看懂的。
image.png