- 引入依赖
- spring-boot-starter-data-elasticsearch
配置Elasticsearch - cluster-name、cluster-nodes.
- spring-boot-starter-data-elasticsearch
- Spring Data Elasticsearch
- ElasticsearchTemplate
- ElasticsearchRepository
实现对帖子的搜索,将帖子的数据存到ES中
EslaticSearch使用
- 下载依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
- 配置文件
在application.properties追加配置
# ElasticsearchProperties
spring.data.elasticsearch.cluster-name=nowcoder
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300
(节点的ip地址和端口,es有两个端口,9200是http访问端口,9300是tcp访问)
- 报错情况
因此还需要再配置
- CommunityApplication.java
package com.nowcoder.community;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class CommunityApplication {
@PostConstruct//管理bean的生命周期,构造器调用完后执行,一般是初始化时,且这个bean是最先被初始化的。
public void init() {
// 解决netty启动冲突问题
// see Netty4Utils.setAvailableProcessors()
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
public static void main(String[] args) {
SpringApplication.run(CommunityApplication.class, args);
}
}
- 将实体类与ES索引关联起来
package com.nowcoder.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;
//自动把实体映射到es的索引中对应,
@Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3)//discusspost索引的名字,shards = 6分片;replicas = 3备份
public class DiscussPost {
@Id//设置数据库属性与索引字段的对应关系
private int id;
@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")
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;
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;
}
public double getScore() {
return score;
}
public void setScore(double 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 +
", score=" + score +
'}';
}
}
- 定义数据访问层的方法
dao下专门定义一个Elastcsearch包,
@Mapper是spring中专门用来定义mybatis数据库的注解,而spring是@repository是专门提供的数据访问层的接口(默认)
package com.nowcoder.community.dao.elasticsearch;
import com.nowcoder.community.entity.DiscussPost;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {//声明实体类是谁,主键的类型
//spring会自动创建好实现类,直接调用即可。
}
- 测试用法
package com.nowcoder.community;
import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.dao.elasticsearch.DiscussPostRepository;
import com.nowcoder.community.entity.DiscussPost;
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 {
@Autowired
private DiscussPostMapper discussMapper;//从mysql中取数据需要用到
@Autowired
private DiscussPostRepository discussRepository;//要在ES中存储数据
@Autowired
private ElasticsearchTemplate elasticTemplate;
//有些特殊情况discussRepository无法处理,需要此对象
@Test//在测试之前,需要检查,ES数据库中有没有对应的索引。往ES中插入数据,数据从mysql中取到
public void testInsert() {//注意要启动elasticsearch.bat
discussRepository.save(discussMapper.selectDiscussPostById(241));//插入单条数据
discussRepository.save(discussMapper.selectDiscussPostById(242));
discussRepository.save(discussMapper.selectDiscussPostById(243));
}
@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));
discussRepository.saveAll(discussMapper.selectDiscussPosts(131, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(132, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(133, 0, 100));
discussRepository.saveAll(discussMapper.selectDiscussPosts(134, 0, 100));
}
@Test
public void testUpdate() {//修改数据
DiscussPost post = discussMapper.selectDiscussPostById(231);
post.setContent("我是新人,使劲灌水.");
discussRepository.save(post);//覆盖
}
@Test
public void testDelete() {
// discussRepository.deleteById(231);
discussRepository.deleteAll();
}
@Test//演示最核心的搜索功能,并且对搜索结果进行高亮显示
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>"),//在前后加入标签,达到高亮的结果(对标签加CSS样式即可)
new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
).build();//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.queryForPage(searchQuery, class, SearchResultMapper)
// 底层获取得到了高亮显示的值, 但是没有返回.查到的数据没有将两份数据处理,
// 一份是高亮显示的值,另一份是原始数据
@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// 解决上述问题的办法就是使用elasticTemplate做搜索;queryForPage会把得到的结果输送到new SearchResultMapper(),
// 而这个mapper会将高亮的结果封装返回给response
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) {//每得到一个命中的数据,都包装到实体类中去
//返回的是JSON数据,在这个hit对象中,将JSON数据转化成了map(key,value)
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();
//获取原始的title,要先查找有高亮的帖子后,
// 再统一将高亮的部分设置,不能一开始就取高亮部分,因为可能没有获取到值
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());//getFragments()返回的是一个数组,只高亮第一段,可能title中有很多命中的字段
}
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());
/**
* 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);
}
}
}
返回的结果是一小段,不是一整篇文章,是合理的。