当前位置:   article > 正文

比较详细的Springboot整合Elasticsearch使用ElasticsearchRestTemplate处理搜索结果的分段显示、高光显示_elasticsearchresttemplate.search

elasticsearchresttemplate.search

1. 前言

         前一段时间我写过一篇文章,也是关于Elasticsearch的,那篇文章使用的方法确实邪门,用的是ElasticsearchRepository的自动依据方法名返回搜索结果,比如在ElasticsearchRepository接口中定义一个名为findPostByTitleOrContent(),Elasticsearch就会返回内容或结果相关的帖子,但是分词、高光、分段全部失效了,只能返回全词匹配的结果,算是一个半成品,所以我改进了一下,研究了源码之后,在chatGPT的辅助下,我终于完美实现了所有的功能,下面我会从安装开始,详细的阐述一下Elasticsearch的用法,话不多说,直接开始。

2. 配置准备

2.1 安装Elasticsearch

        这里我使用的是7.15.2的版本,适配2.6.11版本的Springboot(因为我使用的是jdk1.8),如果需要更高版本则需要自己去官网找对应的版本号。

        这里附上下载地址Past Releases of Elastic Stack Software | Elastic,如果速度比较慢可以挂个梯子。

        下载完成以后就是按部就班的安装,安装完成以后可以顺便配置一下Elasticsearch的环境变量,方便后续的一些操做,可以用

curl -X GET "localhost:9200/_cat/health?v"

 检验是否安装成功,如果成功就会像下面这样,可以看到服务器的健康程度:

       

        确认成功安装就可以进入安装目录,打开elasticsearch.bat就可以启动Elasticsearch了,但是我想弄点骚操作,因为项目整合了Kafka以后每次想要运行项目都要先启动Kafka敲一长串命令,所以我想偷个懒,写个bat来自动执行,顺便也把执行elasticsearch.bat加进去一次性启动:

  1. @echo off
  2. cd /d D:\kafka\kafka_2.13-3.4.0
  3. start /B bin\windows\zookeeper-server-start.bat config\zookeeper.properties
  4. start /B bin\windows\kafka-server-start.bat config\server.properties
  5. cd /d D:\elasticsearch\elasticsearch-7.15.2\bin
  6. start /B elasticsearch.bat

        将上面的内容复制到txt文件中,参照我上面的目录修改自己的安装目录,然后修改文件后缀为.bat即可食用,注意: 要先把Kafka和Zookeeper添加到环境变量,否则可能不能运行。

        但是要像愉快的使用中文搜索,我们还需要安装ik分词

直接上链接:Releases · medcl/elasticsearch-analysis-ik · GitHub

        选择7.15.2

        然后把下载好的ik放到elasticsearch的plugins目录下就可以了:

 

 

2.2 Springboot的配置

        首先注入依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  4. </dependency>

        其次是配置application.properties:

  1. #ElasticsearchProperties
  2. spring.data.elasticsearch.repositories.enabled=true

3. 实体类的处理

        下面以实体类帖子为例,演示一下实体类的处理:

  1. package com.newcoder.community.entity;
  2. import org.springframework.data.annotation.Id;
  3. import org.springframework.data.elasticsearch.annotations.Document;
  4. import org.springframework.data.elasticsearch.annotations.Field;
  5. import org.springframework.data.elasticsearch.annotations.FieldType;
  6. import java.util.Date;
  7. @Document(indexName = "discusspost", shards = 6 ,replicas = 3)
  8. public class DiscussPost {
  9. // Elasticsearch的ID
  10. @Id
  11. private int id;
  12. // Elasticsearch映射数据类型
  13. @Field(type = FieldType.Integer)
  14. private int userId;
  15. // 这里的analyzer是分词器,searchAnalyzer指的是分词策略
  16. // 如果需要将某个属性作为关键字那么就需要加上分词器与分词策略
  17. @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
  18. private String title;
  19. @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
  20. // 分词器analyzer
  21. private String content;
  22. @Field(type = FieldType.Integer)
  23. private int type;
  24. @Field(type = FieldType.Integer)
  25. private int status;
  26. @Field(type = FieldType.Date)
  27. private Date createTime;
  28. @Field(type = FieldType.Integer)
  29. private int commentCount;
  30. @Field(type = FieldType.Double)
  31. private int score;
  32. public int getScore() {
  33. return score;
  34. }
  35. public void setScore(int score) {
  36. this.score = score;
  37. }
  38. @Override
  39. public String toString() {
  40. return "DiscussPost{" +
  41. "id=" + id +
  42. ", userId=" + userId +
  43. ", title='" + title + '\'' +
  44. ", content='" + content + '\'' +
  45. ", type=" + type +
  46. ", status=" + status +
  47. ", createTime=" + createTime +
  48. ", commentCount=" + commentCount +
  49. ", commentCount=" + score +
  50. '}';
  51. }
  52. public int getId() {
  53. return id;
  54. }
  55. public void setId(int id) {
  56. this.id = id;
  57. }
  58. public int getUserId() {
  59. return userId;
  60. }
  61. public void setUserId(int userId) {
  62. this.userId = userId;
  63. }
  64. public String getTitle() {
  65. return title;
  66. }
  67. public void setTitle(String title) {
  68. this.title = title;
  69. }
  70. public String getContent() {
  71. return content;
  72. }
  73. public void setContent(String content) {
  74. this.content = content;
  75. }
  76. public int getType() {
  77. return type;
  78. }
  79. public void setType(int type) {
  80. this.type = type;
  81. }
  82. public int getStatus() {
  83. return status;
  84. }
  85. public void setStatus(int status) {
  86. this.status = status;
  87. }
  88. public Date getCreateTime() {
  89. return createTime;
  90. }
  91. public void setCreateTime(Date createTime) {
  92. this.createTime = createTime;
  93. }
  94. public int getCommentCount() {
  95. return commentCount;
  96. }
  97. public void setCommentCount(int commentCount) {
  98. this.commentCount = commentCount;
  99. }
  100. }

4. Dao层的处理

        主要是定义一个接口,用来声明需要存取的实体类,这里是DiscussPost

  1. package com.newcoder.community.dao.elasticsearch;
  2. import com.newcoder.community.entity.DiscussPost;
  3. import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
  4. import org.springframework.stereotype.Repository;
  5. @Repository
  6. public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
  7. }

        声明完这个接口以后方便后续调用Elasticsearch的save()方法和delete()方法还有update()方法来对Elasticsearch数据库中的内容进行增删改操作。

5. Service层的处理

5.1 前期准备

        在这里我为了让方法可以同时返回帖子和帖子条数(因为调用.size()方法得到了错误的帖子条数,导致搜素结果大量缺失)新建了一个类来存储帖子和帖子条数:

  1. package com.newcoder.community.entity;
  2. import java.util.List;
  3. public class SearchResult {
  4. private long rows;
  5. private List<DiscussPost> posts;
  6. public SearchResult(long rows, List<DiscussPost> posts) {
  7. this.rows = rows;
  8. this.posts = posts;
  9. }
  10. public long getRows() {
  11. return rows;
  12. }
  13. public void setRows(long rows) {
  14. this.rows = rows;
  15. }
  16. public List<DiscussPost> getPosts() {
  17. return posts;
  18. }
  19. public void setPosts(List<DiscussPost> posts) {
  20. this.posts = posts;
  21. }
  22. }

 5.2 使用  ElasticsearchRestTemplate获得并处理搜索结果  

        因为ElasticsearchTemplate被弃用了,所以我选择使用的是ElasticsearchRestTemplate来完成构建Service层。

  1. package com.newcoder.community.services;
  2. import com.newcoder.community.dao.DiscussPostMapper;
  3. import com.newcoder.community.dao.elasticsearch.DiscussPostRepository;
  4. import com.newcoder.community.entity.DiscussPost;
  5. import com.newcoder.community.entity.SearchResult;
  6. import org.elasticsearch.index.query.QueryBuilders;
  7. import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
  8. import org.elasticsearch.search.sort.SortBuilders;
  9. import org.elasticsearch.search.sort.SortOrder;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.data.domain.Pageable;
  12. import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
  13. import org.springframework.data.elasticsearch.core.SearchHit;
  14. import org.springframework.data.elasticsearch.core.SearchHits;
  15. import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
  16. import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
  17. import org.springframework.stereotype.Service;
  18. import javax.annotation.PostConstruct;
  19. import java.util.ArrayList;
  20. import java.util.Date;
  21. import java.util.List;
  22. @Service
  23. public class SearchService {
  24. @Autowired
  25. private DiscussPostRepository discussPostRepository;
  26. @Autowired
  27. private DiscussPostMapper discussPostMapper;
  28. @Autowired
  29. private ElasticsearchRestTemplate elasticsearchRestTemplate;
  30. /*
  31. 每次项目重新部署就把mysql里的帖子全部导入elasticsearch
  32. */
  33. @PostConstruct
  34. public void init() {
  35. discussPostRepository.deleteAll();
  36. System.out.println("自动删除所有帖子");
  37. discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(0,0,discussPostMapper.selectDiscussPostRows(0)));
  38. System.out.println("自动注入所有帖子");
  39. }
  40. public SearchResult search(String keyword, Pageable pageable) {
  41. List<DiscussPost> posts = new ArrayList<>();
  42. // 构建一个NativeSearchQuery并添加分页条件、排序条件以及高光区域
  43. NativeSearchQuery query = new NativeSearchQueryBuilder()
  44. .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
  45. .withPageable(pageable)
  46. .withSorts(
  47. SortBuilders.fieldSort("type").order(SortOrder.DESC),
  48. SortBuilders.fieldSort("score").order(SortOrder.DESC),
  49. SortBuilders.fieldSort("createTime").order(SortOrder.DESC)
  50. )
  51. .withHighlightFields(
  52. new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
  53. new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
  54. )
  55. .build();
  56. // 调用ElasticsearchRestTemplate的search()方法进行查询
  57. // 使用SearchHits存储搜索结果
  58. SearchHits<DiscussPost> searchHits = elasticsearchRestTemplate.search(query, DiscussPost.class);
  59. long rows = searchHits.getTotalHits();
  60. // 遍历搜索结果设置帖子的各个参数
  61. if (searchHits.getTotalHits() != 0) {
  62. for (SearchHit<DiscussPost> searchHit : searchHits) {
  63. DiscussPost post = new DiscussPost();
  64. int id = searchHit.getContent().getId();
  65. post.setId(id);
  66. int userId = searchHit.getContent().getUserId();
  67. post.setUserId(userId);
  68. String title = searchHit.getContent().getTitle();
  69. post.setTitle(title);
  70. String content = searchHit.getContent().getContent();
  71. post.setContent(content);
  72. int status = searchHit.getContent().getStatus();
  73. post.setStatus(status);
  74. int type = searchHit.getContent().getType();
  75. post.setType(type);
  76. Date createTime = searchHit.getContent().getCreateTime();
  77. post.setCreateTime(createTime);
  78. int commentCount = searchHit.getContent().getCommentCount();
  79. post.setCommentCount(commentCount);
  80. // 获得刚刚构建的高光区域,填到帖子的内容和标题上
  81. List<String> contentField = searchHit.getHighlightFields().get("content");
  82. if (contentField != null) {
  83. post.setContent(contentField.get(0));
  84. }
  85. List<String> titleField = searchHit.getHighlightFields().get("title");
  86. if (titleField != null) {
  87. post.setTitle(titleField.get(0));
  88. }
  89. posts.add(post);
  90. }
  91. }
  92. return new SearchResult(rows, posts);
  93. }
  94. public void saveDiscussPost(DiscussPost discussPost) {
  95. discussPostRepository.save(discussPost);
  96. }
  97. public void deleteDiscussPost(DiscussPost discussPost) {
  98. discussPostRepository.delete(discussPost);
  99. }
  100. }

        值得一提的是,貌似在7.x之后Elasticsearch就把getHighlightFields().get()方法返回类型设置为了List,但是很智能的将分段和高光全部集成了,所以还是很简单的。

6. Controller层

        这一层比较简单,没啥好说的

  1. package com.newcoder.community.controller;
  2. import com.newcoder.community.entity.DiscussPost;
  3. import com.newcoder.community.entity.Page;
  4. import com.newcoder.community.entity.User;
  5. import com.newcoder.community.services.LikeService;
  6. import com.newcoder.community.services.SearchService;
  7. import com.newcoder.community.services.UserService;
  8. import com.newcoder.community.util.CommunityConstant;
  9. import com.newcoder.community.util.CommunityUtil;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.data.domain.PageRequest;
  12. import org.springframework.data.domain.Pageable;
  13. import org.springframework.data.domain.Sort;
  14. import org.springframework.stereotype.Controller;
  15. import org.springframework.ui.Model;
  16. import org.springframework.web.bind.annotation.RequestMapping;
  17. import org.springframework.web.bind.annotation.RequestMethod;
  18. import java.util.ArrayList;
  19. import java.util.HashMap;
  20. import java.util.List;
  21. import java.util.Map;
  22. @Controller
  23. public class SearchController implements CommunityConstant {
  24. @Autowired
  25. private SearchService searchService;
  26. @Autowired
  27. private UserService userService;
  28. @Autowired
  29. private LikeService likeService;
  30. @RequestMapping(path = "/search", method = RequestMethod.GET)
  31. public String search(String keyword, Model model, Page page) {
  32. Sort sort = CommunityUtil.getSearchSort();
  33. Pageable pageable = PageRequest.of(page.getCurrent() - 1, page.getLimit(),sort);
  34. page.setPath("/search?keyword=" + keyword);
  35. long rows = searchService.search(keyword, pageable).getRows();
  36. page.setRows((int) rows);
  37. List<DiscussPost> list = searchService.search(keyword, pageable).getPosts();
  38. List<Map<String,Object>> discussPosts = new ArrayList<>();
  39. if (list != null) {
  40. for (DiscussPost discussPost : list) {
  41. Map<String,Object> map = new HashMap<>();
  42. map.put("post",discussPost);
  43. User user = userService.findUserById(discussPost.getUserId());
  44. map.put("user",user);
  45. long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPost.getId());
  46. map.put("likeCount",likeCount);
  47. discussPosts.add(map);
  48. }
  49. }
  50. model.addAttribute("discussPosts",discussPosts);
  51. model.addAttribute("keyword",keyword);
  52. model.addAttribute("rows",rows);
  53. return "site/search";
  54. }
  55. }

7. 最终效果

7.1 单一关键词

7.2 多关键词

      

8. 后记

         这篇文章是小白学习路上的一些记录,如果能帮助到你的话,请帮我点个赞!

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

闽ICP备14008679号