文章目录
- 什么是Elasticsearch?
- Spring是怎么整合Elasticsearch的?
- 开发社区搜索功能
Elasticsearch实现全文搜索功能
什么是Elasticsearch?
-
Elasticsearch简介
- 一个分布式的、Restful风格的搜索引擎
- 支持对各种类型的数据的检索
- 搜索速度快,可以提供实时的搜索服务
- 便于水平扩展,可以处理PB级海量数据
-
Elasticsearch术语
- 索引、类型、文档、字段 (与 mysql中的数据库、表、行、列相对应,ES7.0以后,废弃掉了类型的概念,索引对应表,文档对应行,字段对应列)
- 集群、节点、分片、副本
-
Elasticsearch的下载安装
下载elasticsearch安装包,解压缩。修改config/elasticsearch.yml文件中的cluster.name、path.data、path.logs。下载ik中文分词器(springboot、elasticsearch、ik 版本要对应),在plugins文件夹下创建名为ik的文件夹,将下载的中文分词器解压缩到新建的ik文件夹下。执行 ./bin/elasticsearch启动ES服务。
Spring是怎么整合Elasticsearch的?
第一步:引入依赖
- spring-boot-starter-data-elasticsearch
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
第二步:配置Elasticsearch
- cluster-name、cluster-nodes
# ElasticsearchProperties
spring.data.elasticsearch.cluster-name=amelia # 这个就是es配置文件中配置的cluster.name
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
⚠️ :elasticsearch有两个默认端口,9200是http访问,9300是tcp访问
⚠️:由于redis和es底层都使用了netty,所以会有netty启动冲突的问题,需要在启动类中进行一些配置,详见 CommunityApplication
@PostConstruct
public void init(){
// 解决netty启动冲突的问题
// Netty4Utils.setAvailableProcessors()
System.setProperty("es.set.netty.runtime.available.processors","false");
}
第三步:调用Spring提供的关于ES的API,访问ES服务器(Spring Data Elasticsearch)
-
ElasticsearchTemplate(类)
-
ElasticsearchRepository(接口)——这种方案更简单一点,优先选择这个
-
示例:
- 对实体类添加@Ducument注解,对实体中的属性也需要添加注解
// 示例 @Document(indexName = "discusspost",type = "_doc",shards = 6,replicas = 3) public class DiscussPost { @Id private int id; @Field(type = FieldType.Integer) private int userId; @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") 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 double score; }
- 定义ESRepository接口
@Repository public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost,Integer> { //DiscussPost是数据的类型,Integer是主键的类型 }
- 使用 定义的接口 调用函数操作es服务器(对es服务器中的数据进行增删)
// 示例 // 添加一条数据 discussRepository.save(discussPostMapper.selectDiscussPostById(241)); // 添加一组数据 discussRepository.saveAll(discussPostMapper.selectDiscussPosts(101,0,100,0)); // 修改数据 DiscussPost post = discussPostMapper.selectDiscussPostById(231); discussRepository.save(post); // 删除一条数据 discussRepository.deleteById(231); // 删除全部数据 discussRepository.deleteAll();
- 搜索,利用定义的接口进行搜索。首先需要构造搜索的条件(利用SearchQuery接口),然后使用接口的search方法查询结果。
// 示例 public void testSearchByRepository(){ SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬","title","content")) .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC)) .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC)) .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)) .withPageable(PageRequest.of(0,10)) .withHighlightFields( new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"), new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>") ).build(); // elasticTemplate.queryForPage(searchQuery,class,SearchResultMapper) // 底层获取得到了高亮显示的值,但是没有返回 Page<DiscussPost> page = discussRepository.search(searchQuery); System.out.println(page.getTotalElements()); System.out.println(page.getTotalPages()); System.out.println(page.getNumber()); System.out.println(page.getSize()); for(DiscussPost post : page){ System.out.println(post); } }
- 搜索,利用elasticTemplate进行搜索。(首先需要构造搜索的条件,然后进行结果查询,对搜索得到的结果要做一个处理,实现高亮显示)
// 示例 public void testSearchByTemplate(){ SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.multiMatchQuery("互联网寒冬","title","content")) .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC)) .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC)) .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC)) .withPageable(PageRequest.of(0,10)) .withHighlightFields( new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"), new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>") ).build(); Page<DiscussPost> page = elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() { @Override public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) { SearchHits hits = response.getHits(); if(hits.getTotalHits() <= 0){ return null; } List<DiscussPost> list = new ArrayList<>(); for(SearchHit hit:hits){ DiscussPost post = new DiscussPost(); String id = hit.getSourceAsMap().get("id").toString(); post.setId(Integer.valueOf(id)); String userId = hit.getSourceAsMap().get("userId").toString(); post.setUserId(Integer.valueOf(userId)); String title = hit.getSourceAsMap().get("title").toString(); post.setTitle(title); String content = hit.getSourceAsMap().get("content").toString(); post.setContent(content); String status = hit.getSourceAsMap().get("status").toString(); post.setStatus(Integer.valueOf(status)); String createTime = hit.getSourceAsMap().get("createTime").toString(); post.setCreateTime(new Date(Long.valueOf(createTime))); String commentCount = hit.getSourceAsMap().get("commentCount").toString(); post.setCommentCount(Integer.valueOf(commentCount)); // 处理高亮显示的结果 HighlightField titleField = hit.getHighlightFields().get("title"); if(titleField != null){ post.setTitle(titleField.getFragments()[0].toString()); } HighlightField contentField = hit.getHighlightFields().get("content"); if(contentField != null){ post.setContent(contentField.getFragments()[0].toString()); } list.add(post); } return new AggregatedPageImpl(list,pageable, hits.getTotalHits(),response.getAggregations(),response.getScrollId(),hits.getMaxScore()); } }); System.out.println(page.getTotalElements()); System.out.println(page.getTotalPages()); System.out.println(page.getNumber()); System.out.println(page.getSize()); for(DiscussPost post : page){ System.out.println(post); } }
开发社区搜索功能
-
搜索服务
- 发布帖子的时候,将帖子保存至Elasticsearch服务器。
- 从Elasticsearch服务器删除帖子。
- 从Elasticsearch服务器搜索帖子
-
采用事件的方式来处理,发布事件
- 发布帖子时,将帖子异步的提交到Elasticsearch服务器
- 增加评论时,将帖子异步的提交到Elasticsearch服务器
- 在消费组件中增加一个方法,消费帖子发布事件
-
显示结果
- 在控制器中处理搜索请求,在HTML上显示搜索结果
-
实现细节:
-
首先,要对entity中的实体类discusspost进行一些注解,在数据访问层创建一个DiscussPostRepository的接口,service层创建一个elasticsearch的服务类(ElasticsearchService),在表现层创建一个SearchController类,处理搜索功能。
-
然后,在EventConsumer中创建一个消费发帖的事件【这个事件,主要就是用于把帖子存到Elasticsearch中】,分别在CommentController的发布评论方法和DiscussPostController的发布帖子方法中添加触发 发帖事件 的代码段。
-
还有一点需要注意,由于redis和es底层都使用了netty,所以会有netty启动冲突的问题,需要在启动类中进行一些配置,详见 CommunityApplication
-