SpringBoot2.6.x整合Elasticsearch7.17
Elasticsearch 简介:
- 一个分布式的,Restful 风格的搜索引擎。
- 支持对各种类型的数据的检索。
- 搜索速度快,可以提供实时的搜索服务。
- 便于水平扩展,每秒可以处理 PB 级的海量数据。
Elasticsearch 术语:
- 索引,类型,文档,字段
- 集群,节点,分片,副本
要想使用 es 搜索数据,前提是要在 es 中把数据再存一份。
- 索引(index):一个索引就是一个拥有几分相似特征的文档的集合(可以类比MySQL中的一个数据库)。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。 在一个集群中,如果你想,可以定义任意多的索引。
- 类型(type):在一个索引中,你可以定义一种或多种类型(可以类比为MySQL数据库中的一张表,不过在新版本中type已经被废弃)。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。
- 文档(document): 一个文档是一个可被索引的基础信息单元(可以类比MySQL表中的一条数据)。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。
- 字段(field): JSON 风格的每一个属性叫做字段,对应与表中的列名。
- 集群(cluster):一个集群就是由一个或多个节点组织在一起,它们共同持有你整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。他们协同工作,共享数据并提供故障转移和扩展功能,当然一个节点也可以组成一个集群。
- 节点(node):节点,一个运行的 ES 实例就是一个节点(也就是你集群中的一个服务器),作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。 在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。
- 分片(shards): 一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。所以一个索引在存储的时候就可以拆成多个分片,每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。这样就大大提高了并发能力。
- 副本(replicas):副本是对分片的备份(拷贝),一个分片可以包含多个副本。万一哪份备份数据出现了问题,这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的,这就可以创建分片的一份或多份拷贝,提高了系统的可用性。
使用SpringBoot2.6.x集成Elasticsearch7.17
- pom 文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- application.properties配置
# ElasticsearchProperties
elasticsearch.host=127.0.0.1
elasticsearch.port=9200
- Elasticsearch配置类
/**
* Es配置类
* @ConfigurationProperties(prefix = "elasticsearch")
* 自动读取关联application.properties中前缀为elasticsearch的属性
*/
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
private String host;
private Integer port;
// 重写父类方法
@Override
public RestHighLevelClient elasticsearchClient() {
RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
return restHighLevelClient;
}
}
- 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "discusspost", shards = 6, replicas = 3)
public class DiscussPost {
@Id
private int id;
// 用户id
@Field(type = FieldType.Integer)
private int userId;
// 帖子的标题 eg.互联网校招
@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;
// type为帖子类型,0--普通帖子,1--置顶帖子,2--精华帖子
@Field(type = FieldType.Integer)
private int type;
// 帖子的状态,0--正常,1--精华,2--拉黑
@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;
}
- 创建继承Elasticsearch的接口
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
}
- 创建测试类
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CommunityApplication.class)
@SpringBootTest
public class ElasticsearchTests {
@Autowired
private DiscussPostMapper discussPostMapper;
// 实现crud等操作可以使用继承ElasticsearchRepository的接口,也可以使用ElasticsearchRestTemplate
// 注入ElasticsearchRepository
@Autowired
private DiscussPostRepository discussPostRepository;
// 注入ElasticsearchRestTemplate
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
// 插入一条数据
@Test
public void testInsert() {
discussPostRepository.save(discussPostMapper.selectDiscussPostById(241));
discussPostRepository.save(discussPostMapper.selectDiscussPostById(242));
discussPostRepository.save(discussPostMapper.selectDiscussPostById(243));
elasticsearchRestTemplate.save();
}
// 插入多条数据
@Test
public void testInsertList() {
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(101, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(102, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(103, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(111, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(112, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(131, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(132, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(133, 0, 100));
discussPostRepository.saveAll(discussPostMapper.selectDiscussPosts(134, 0, 100));
}
// 修改一条数据
@Test
public void testUpdate() {
DiscussPost post = discussPostMapper.selectDiscussPostById(231);
post.setContent("SpringBoot最新教程,快来围观!");
discussPostRepository.save(post);
}
// 删除一条(多条)数据
@Test
public void testDelete() {
discussPostRepository.delete(discussPostMapper.selectDiscussPostById(231));
//discussPostRepository.deleteAll();
}
// 查询数据
@Test
public void testSearchByRepository() {
// withQuery()用于构造搜索条件
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
.withSorts(SortBuilders.fieldSort("type").order(SortOrder.DESC),
SortBuilders.fieldSort("score").order(SortOrder.DESC),
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();
Iterable<DiscussPost> discussPosts = discussPostRepository.findAll();
for (DiscussPost post : discussPosts) {
System.out.println(post);
}
}
// 查询数据并将关键字高亮显示
@Test
public void testSearchByTemplate() {
// withQuery()用于构造搜索条件
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
.withSorts(SortBuilders.fieldSort("type").order(SortOrder.DESC),
SortBuilders.fieldSort("score").order(SortOrder.DESC),
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();
//查询
SearchHits<DiscussPost> search = elasticsearchRestTemplate.search(searchQuery, DiscussPost.class);
long count = elasticsearchRestTemplate.count(searchQuery, DiscussPost.class);
System.out.println("共查询到: "+ count +"条数据");
//得到查询返回的内容
List<SearchHit<DiscussPost>> searchHits = search.getSearchHits();
// 设置一个最后需要返回的实体类集合
List<DiscussPost> discussPosts = new ArrayList<>();
// 遍历返回的内容,进行处理
for (SearchHit<DiscussPost> hit : searchHits) {
// 获取高亮的内容
Map<String, List<String>> highlightFields = hit.getHighlightFields();
// 将高亮的内容添加到content中(匹配到的如果是多段,就将第一段高亮显示)
// 没有匹配到关键字就显示原来的title和content
hit.getContent().setTitle(highlightFields.get("title")==null ? hit.getContent().getTitle():highlightFields.get("title").get(0));
hit.getContent().setContent(highlightFields.get("content")==null ? hit.getContent().getContent():highlightFields.get("content").get(0));
// 放到实体类中
discussPosts.add(hit.getContent());
}
for (DiscussPost post : discussPosts) {
System.out.println(post);
}
}
}