赞
踩
前一段时间我写过一篇文章,也是关于Elasticsearch的,那篇文章使用的方法确实邪门,用的是ElasticsearchRepository的自动依据方法名返回搜索结果,比如在ElasticsearchRepository接口中定义一个名为findPostByTitleOrContent(),Elasticsearch就会返回内容或结果相关的帖子,但是分词、高光、分段全部失效了,只能返回全词匹配的结果,算是一个半成品,所以我改进了一下,研究了源码之后,在chatGPT的辅助下,我终于完美实现了所有的功能,下面我会从安装开始,详细的阐述一下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加进去一次性启动:
- @echo off
- cd /d D:\kafka\kafka_2.13-3.4.0
- start /B bin\windows\zookeeper-server-start.bat config\zookeeper.properties
- start /B bin\windows\kafka-server-start.bat config\server.properties
- cd /d D:\elasticsearch\elasticsearch-7.15.2\bin
- start /B elasticsearch.bat
将上面的内容复制到txt文件中,参照我上面的目录修改自己的安装目录,然后修改文件后缀为.bat即可食用,注意: 要先把Kafka和Zookeeper添加到环境变量,否则可能不能运行。
但是要像愉快的使用中文搜索,我们还需要安装ik分词:
直接上链接:Releases · medcl/elasticsearch-analysis-ik · GitHub
选择7.15.2
然后把下载好的ik放到elasticsearch的plugins目录下就可以了:
首先注入依赖:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
- </dependency>
其次是配置application.properties:
- #ElasticsearchProperties
- spring.data.elasticsearch.repositories.enabled=true
下面以实体类帖子为例,演示一下实体类的处理:
- package com.newcoder.community.entity;
- import org.springframework.data.annotation.Id;
- import org.springframework.data.elasticsearch.annotations.Document;
- import org.springframework.data.elasticsearch.annotations.Field;
- import org.springframework.data.elasticsearch.annotations.FieldType;
- import java.util.Date;
- @Document(indexName = "discusspost", shards = 6 ,replicas = 3)
- public class DiscussPost {
- // Elasticsearch的ID
- @Id
- private int id;
-
- // Elasticsearch映射数据类型
- @Field(type = FieldType.Integer)
- private int userId;
- // 这里的analyzer是分词器,searchAnalyzer指的是分词策略
- // 如果需要将某个属性作为关键字那么就需要加上分词器与分词策略
- @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
- private String title;
- @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
- // 分词器analyzer
- private String content;
- @Field(type = FieldType.Integer)
- private int type;
- @Field(type = FieldType.Integer)
- private int status;
- @Field(type = FieldType.Date)
- private Date createTime;
- @Field(type = FieldType.Integer)
- private int commentCount;
- @Field(type = FieldType.Double)
- private int score;
-
- public int getScore() {
- return score;
- }
-
- public void setScore(int score) {
- this.score = score;
- }
-
- @Override
- public String toString() {
- return "DiscussPost{" +
- "id=" + id +
- ", userId=" + userId +
- ", title='" + title + '\'' +
- ", content='" + content + '\'' +
- ", type=" + type +
- ", status=" + status +
- ", createTime=" + createTime +
- ", commentCount=" + commentCount +
- ", commentCount=" + score +
- '}';
- }
-
- public int getId() {
- return id;
- }
-
- public void setId(int id) {
- this.id = id;
- }
-
- public int getUserId() {
- return userId;
- }
-
- public void setUserId(int userId) {
- this.userId = userId;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getContent() {
- return content;
- }
-
- public void setContent(String content) {
- this.content = content;
- }
-
- public int getType() {
- return type;
- }
-
- public void setType(int type) {
- this.type = type;
- }
-
- public int getStatus() {
- return status;
- }
-
- public void setStatus(int status) {
- this.status = status;
- }
-
- public Date getCreateTime() {
- return createTime;
- }
-
- public void setCreateTime(Date createTime) {
- this.createTime = createTime;
- }
-
- public int getCommentCount() {
- return commentCount;
- }
-
- public void setCommentCount(int commentCount) {
- this.commentCount = commentCount;
- }
- }
主要是定义一个接口,用来声明需要存取的实体类,这里是DiscussPost
- package com.newcoder.community.dao.elasticsearch;
- import com.newcoder.community.entity.DiscussPost;
- import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
- import org.springframework.stereotype.Repository;
-
- @Repository
- public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
- }
声明完这个接口以后方便后续调用Elasticsearch的save()方法和delete()方法还有update()方法来对Elasticsearch数据库中的内容进行增删改操作。
在这里我为了让方法可以同时返回帖子和帖子条数(因为调用.size()方法得到了错误的帖子条数,导致搜素结果大量缺失)新建了一个类来存储帖子和帖子条数:
- package com.newcoder.community.entity;
-
- import java.util.List;
-
- public class SearchResult {
- private long rows;
- private List<DiscussPost> posts;
-
- public SearchResult(long rows, List<DiscussPost> posts) {
- this.rows = rows;
- this.posts = posts;
- }
-
- public long getRows() {
- return rows;
- }
-
- public void setRows(long rows) {
- this.rows = rows;
- }
-
- public List<DiscussPost> getPosts() {
- return posts;
- }
-
- public void setPosts(List<DiscussPost> posts) {
- this.posts = posts;
- }
- }
因为ElasticsearchTemplate被弃用了,所以我选择使用的是ElasticsearchRestTemplate来完成构建Service层。
- package com.newcoder.community.services;
- import com.newcoder.community.dao.DiscussPostMapper;
- import com.newcoder.community.dao.elasticsearch.DiscussPostRepository;
- import com.newcoder.community.entity.DiscussPost;
- import com.newcoder.community.entity.SearchResult;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
- import org.elasticsearch.search.sort.SortBuilders;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.domain.Pageable;
- 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.NativeSearchQuery;
- import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
- import org.springframework.stereotype.Service;
- import javax.annotation.PostConstruct;
- import java.util.ArrayList;
- import java.util.Date;
- import java.util.List;
-
- @Service
- public class SearchService {
- @Autowired
- private DiscussPostRepository discussPostRepository;
-
- @Autowired
- private DiscussPostMapper discussPostMapper;
- @Autowired
- private ElasticsearchRestTemplate elasticsearchRestTemplate;
-
- /*
- 每次项目重新部署就把mysql里的帖子全部导入elasticsearch
- */
- @PostConstruct
- public void init() {
- discussPostRepository.deleteAll();
- System.out.println("自动删除所有帖子");
- discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(0,0,discussPostMapper.selectDiscussPostRows(0)));
- System.out.println("自动注入所有帖子");
- }
-
- public SearchResult search(String keyword, Pageable pageable) {
- List<DiscussPost> posts = new ArrayList<>();
-
- // 构建一个NativeSearchQuery并添加分页条件、排序条件以及高光区域
- NativeSearchQuery query = new NativeSearchQueryBuilder()
- .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
- .withPageable(pageable)
- .withSorts(
- SortBuilders.fieldSort("type").order(SortOrder.DESC),
- SortBuilders.fieldSort("score").order(SortOrder.DESC),
- SortBuilders.fieldSort("createTime").order(SortOrder.DESC)
- )
- .withHighlightFields(
- new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
- new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
- )
- .build();
- // 调用ElasticsearchRestTemplate的search()方法进行查询
- // 使用SearchHits存储搜索结果
- SearchHits<DiscussPost> searchHits = elasticsearchRestTemplate.search(query, DiscussPost.class);
- long rows = searchHits.getTotalHits();
-
- // 遍历搜索结果设置帖子的各个参数
- if (searchHits.getTotalHits() != 0) {
- for (SearchHit<DiscussPost> searchHit : searchHits) {
- DiscussPost post = new DiscussPost();
-
- int id = searchHit.getContent().getId();
- post.setId(id);
-
- int userId = searchHit.getContent().getUserId();
- post.setUserId(userId);
-
- String title = searchHit.getContent().getTitle();
- post.setTitle(title);
-
- String content = searchHit.getContent().getContent();
- post.setContent(content);
-
- int status = searchHit.getContent().getStatus();
- post.setStatus(status);
-
- int type = searchHit.getContent().getType();
- post.setType(type);
-
- Date createTime = searchHit.getContent().getCreateTime();
- post.setCreateTime(createTime);
-
- int commentCount = searchHit.getContent().getCommentCount();
- post.setCommentCount(commentCount);
-
- // 获得刚刚构建的高光区域,填到帖子的内容和标题上
- List<String> contentField = searchHit.getHighlightFields().get("content");
- if (contentField != null) {
- post.setContent(contentField.get(0));
- }
-
- List<String> titleField = searchHit.getHighlightFields().get("title");
- if (titleField != null) {
- post.setTitle(titleField.get(0));
- }
- posts.add(post);
- }
- }
-
- return new SearchResult(rows, posts);
- }
-
- public void saveDiscussPost(DiscussPost discussPost) {
- discussPostRepository.save(discussPost);
- }
-
- public void deleteDiscussPost(DiscussPost discussPost) {
- discussPostRepository.delete(discussPost);
- }
-
- }
值得一提的是,貌似在7.x之后Elasticsearch就把getHighlightFields().get()方法返回类型设置为了List,但是很智能的将分段和高光全部集成了,所以还是很简单的。
这一层比较简单,没啥好说的
- package com.newcoder.community.controller;
-
- import com.newcoder.community.entity.DiscussPost;
- import com.newcoder.community.entity.Page;
- import com.newcoder.community.entity.User;
- import com.newcoder.community.services.LikeService;
- import com.newcoder.community.services.SearchService;
- import com.newcoder.community.services.UserService;
- import com.newcoder.community.util.CommunityConstant;
- import com.newcoder.community.util.CommunityUtil;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.domain.PageRequest;
- import org.springframework.data.domain.Pageable;
- import org.springframework.data.domain.Sort;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- @Controller
- public class SearchController implements CommunityConstant {
- @Autowired
- private SearchService searchService;
-
- @Autowired
- private UserService userService;
-
- @Autowired
- private LikeService likeService;
-
- @RequestMapping(path = "/search", method = RequestMethod.GET)
- public String search(String keyword, Model model, Page page) {
-
- Sort sort = CommunityUtil.getSearchSort();
- Pageable pageable = PageRequest.of(page.getCurrent() - 1, page.getLimit(),sort);
- page.setPath("/search?keyword=" + keyword);
- long rows = searchService.search(keyword, pageable).getRows();
- page.setRows((int) rows);
-
- List<DiscussPost> list = searchService.search(keyword, pageable).getPosts();
- List<Map<String,Object>> discussPosts = new ArrayList<>();
- if (list != null) {
- for (DiscussPost discussPost : list) {
- Map<String,Object> map = new HashMap<>();
- map.put("post",discussPost);
-
- User user = userService.findUserById(discussPost.getUserId());
- map.put("user",user);
-
- long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, discussPost.getId());
- map.put("likeCount",likeCount);
- discussPosts.add(map);
- }
- }
- model.addAttribute("discussPosts",discussPosts);
- model.addAttribute("keyword",keyword);
- model.addAttribute("rows",rows);
- return "site/search";
- }
-
- }
这篇文章是小白学习路上的一些记录,如果能帮助到你的话,请帮我点个赞!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。