1. 初识Elasticsearch
Elasticsearch入门
- 一个分布式的、Restful风格的搜索引擎。
- 支持对各种类型的数据的检索。
- 搜索速度快,可以提供实时的搜索服务。
- 便于水平扩展,每秒可以处理PB级海量数据。
Elasticsearch术语 :
-
索引、类型、文档、字段。
-
集群、节点、分片、副本。
Elasticsearch与数据库的对比:
| Elasticsearch | 数据库 |
|---|---|
| 索引 | database |
| 类型 | table |
| 文档 | 一行数据 |
| 字段 | 一列数据 |
Elasticsearch6.0后变更
| Elasticsearch | 数据库 |
|---|---|
| 索引 | table |
| 类型 | 逐渐废弃 |
| 文档 | 一行数据 |
| 字段 | 一列数据 |
其他数据
-
集群:多个Elasticsearch组成一个集群
-
节点:集群中每个Elasticsearch为一个节点
-
分片:一个索引在存储时可以分为多个分片
-
副本:是对分片的复制,一个分片有多个副本。提高可用性
2. Spring整合Elasticsearch
引入依赖 :
- spring-boot-starter-data-elasticsearch
配置Elasticsearch:
- cluster-name、cluster-nodes
Spring Data Elasticsearch
-
ElasticsearchTemplate
-
ElasticsearchRepository
2.1 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
2.2 配置Elasticsearch
# 集群的名字
spring.data.elasticsearch.cluster-name=elasticsearch
# 节点的id:端口
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
2.3 解决冲突
elasticsearch和Redis的底层都是基于netty实现,两者在启动时会有冲突。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class CommunityApplication {
@PostConstruct
public void init() {
// 解决netty启动冲突问题
// 可以查看 Netty4Utils.setAvailableProcessors()
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}
2.4 将数据库中的帖子存入Elasticsearch中,便于检索
使用ElasticsearchRepository进行转存:
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;
// SpringBoot整合Elasticsearch会自动将实体与Elasticsearch中的索引进行映射,但是需要进行设置
// indexName:索引的名字
// type:类型,逐渐不再使用
// shards:分片的数量
// replicas:副本的数量
@Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3)
public class DiscussPost {
// 实体的属性和索引中的字段也需要匹配
@Id
private int id;
@Field(type = FieldType.Integer)
private int userId;
// 我们是从标题和内容中进行检索
// type:类型
// analyzer:存储时的解析器,尽可能将内容所拆分,使用ik_max_word进行拆分
// searchAnalyzer:检索时的解析器,不需要最大量拆分,使用ik_smart进行拆分
@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;
}
2.5 创建一个接口
import com.nowcoder.community.entity.DiscussPost;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
// 继承ElasticsearchRepository,声明泛型为要处理的类型
// DiscussPost:需要处理的实体类,Integer:主键的类型
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
}
2.6 测试
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class ElasticsearchTests {
// 需要从mysql数据库中查找帖子
@Autowired
private DiscussPostMapper discussMapper;
// 使用discussRepository进行处理
@Autowired
private DiscussPostRepository discussRepository;
// 有些特殊情况需要利用elasticTemplate进行处理
@Autowired
private ElasticsearchTemplate elasticTemplate;
// 插入一条数据:discussRepository.save()
@Test
public void testInsert() {
discussRepository.save(discussMapper.selectDiscussPostById(241));
discussRepository.save(discussMapper.selectDiscussPostById(242));
discussRepository.save(discussMapper.selectDiscussPostById(243));
}
// 插入多条数据discussRepository.saveAll()
@Test
public void testInsertList() {
discussRepository.saveAll(discussMapper.selectDiscussPosts(101, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(102, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(103, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(111, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(112, 0, 100));
}
// 修改save
@Test
public void testUpdate() {
DiscussPost post = discussMapper.selectDiscussPostById(231);
post.setContent("新内容");
discussRepository.save(post);
}
// 删除:deleteById(), deleteAll()
@Test
public void testDelete() {
// discussRepository.deleteById(231);
discussRepository.deleteAll();
}
// 查询
@Test
public void testSearchByRepository() {
// 构建检索条件以及返回要求
SearchQuery searchQuery = new NativeSearchQueryBuilder()
// QueryBuilders.multiMatchQuery构建检索条件,从title和content检索互联网
.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))
// 分页条件,从第1页开始,查询10条
.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)
// Repository底层调用queryForPage获取得到了高亮显示的值, 但是没有返回.
// 进行分页查询 使用的是 import org.springframework.data.domain.Page;
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);
}
}
@Test
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) {
// 1. 获取命中的数据
SearchHits hits = response.getHits();
// 如果数量小于等于0,直接返回
if (hits.getTotalHits() <= 0) {
return null;
}
// 2. 将数据存入list
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));
// 处理高亮显示的结果
// 获取title中高亮显示的内容
HighlightField titleField = hit.getHighlightFields().get("title");
if (titleField != null) {
post.setTitle(titleField.getFragments()[0].toString());
}
// 获取content中高亮显示的内容
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的基本概念,如索引、类型、文档和字段,并对比了它与数据库的区别。接着,文章详细阐述了如何在Spring项目中整合Elasticsearch,包括引入依赖、配置、解决与Redis的冲突以及将数据库数据存入Elasticsearch以实现高效检索。
1215

被折叠的 条评论
为什么被折叠?



